» Fig Leaf Software Home

We've Got You Covered.

Wednesday, December 10, 2014

4 Marketing Do's and Don'ts When Creating Emails

Email marketing is an affordable, quick, and customizable way to speak to your target audience. It is important to understand the building blocks of how to create an effective email, as well what causes an email to be ineffective. 

Similar to my last post on landing pages, here are 4 do's and don'ts to follow when creating an email.

Do's
  • Keep the message short and focused. Since people receive tons of emails every day and not a lot of time to read it all, make sure your message gets right to the point. 
  • Include some type of call-to-action. Your email will be much more efficient when you can make your audience respond to an imperative need. Your CTA could be to download a whitepaper, register for an event, deadline to receive an offer or discount pricing, etc. Be creative when coming up with calls-to-action.
  • A/B Testing. Test what does and doesn't work for your audience. If you are creating an email campaign and notice the first email didn't receive many response/click-throughs, then try tweaking the wording or give it a different subject line. 
  • Design emails to be responsive. Keep in mind that your email will more than likely be read on a mobile device or tablet, so you want to make sure you message comes across the same as it would on a desktop. 
Don'ts 
  • Use a global unsubscribe button. Instead of using a global unsubscribe button from every email you send, give your audience the option to decide what content they want to receive. Subscription management allows you to save a relationship that you might have burned using a global unsubscribe button.
  • Treat every prospect the same. Make sure you use segmentation and personalization in your email to better fit your email recipients. 
  • Don't send any email without testing it first. By testing your email, you can avoid embarrassment after sending a blast to your database. It also helps to send the test email to someone not working on it to get a fresh pair of eyes in case you missed something. 
  • Don't just send out a sales pitch. Your audience wants something more than just another self-serving promotional email. Give your audience news and information they can use. Mix it up a little and include something for everyone. 
Check out this deep-dive to learn the 12 components of a 5 star email! Download the guide today and start optimizing those emails. 


Friday, December 5, 2014

4 Marketing Do's and Don'ts When Creating Landing Pages

Landing pages are an important asset for marketers to help convert prospects into leads.  I’ve come up with 4 Do’s and Don'ts to help you in creating effective landing pages.

Do’s:

Make your landing page straightforward and simple.
If a prospect doesn’t see what they are looking for when visiting a landing page, I can assure you they won’t stay on the page very long, and then you could lose a potential lead. You want to make sure your page has a well defined header, brief but detailed description, and has a clear call-to-action. This will help guide the visitor through the steps you want them to take.

Show consistency
If the landing page you are creating is a part of a larger marketing campaign, then make sure you show consistency in tone of voice, design and format, and the message you are trying to get across.

Use images
Images grab the attention of your landing page traffic. They also communicate ideas much better than text. You can A/B test different images to see which will provide you the best traffic for a specific landing page.

Close and convert them with a clear call-to-action
The call to action is the step you want your visitor to take. For example this could be asking them to  sign-up for a whitepaper, register for a webinar or even download a product demo. Whatever CTA you decide on, just make sure that it is clear and straightforward to help move them along your conversion funnel.

Don’ts

Don’t give the visitor a reason to navigate away from your landing page
Remove additional navigation from your landing page. This includes links to other services, links back to the homepage, etc. Also, make sure you are directly addressing problems customers are looking to solve by searching you services.

Don’t ask for too much information
Try to keep your forms as efficient as possible. Don’t ask for too much personal information, if it is not needed. Even as a marketer myself I strongly dislike filling out forms, so the shorter the better!

Don’t put your form or main information below the fold
Make sure to keep important information,  as well as the form above the fold. Since you only have a couple of seconds to make an impression on your visitor, you want to make sure that the main message you want to get across, and the path you want to lead them down are first.

Don’t ignore the metrics!
The metrics can tell you a lot about the page, as well as your target audience. You start learning exactly what your target audience is looking for, which in turn helps you build the best landing pages. For example, if you are receiving a lot of visitors to your landing page, but are low on conversions, you may want to do some A/B testing to make sure you are providing what they are looking for.

In summary, a good landing page can help turn a prospect into a lead, and I think we can all agree nothing makes our sales teams more happy than good leads!

Happy Marketing!

Wednesday, December 3, 2014

How Optimization Checks Can Improve Your Google Search Appliance

As a Google Premier Partner, Fig Leaf Software offers periodic reviews of our customers configurations and use of their Google Search Appliance. This involves an online conference where the customer walks us through how they use search in their organization and shows us their current GSA configuration. This session is recorded and used to produce a detailed Optimization Report that includes recommendations from our experts on how the organization can improve their configuration and make better use of their GSA. This report is provided to our customers at no cost, but simply as a benefit of doing business with us. Satisfied customers are an important part of renewals and expanded business relationships. 

In the last couple of months, I've started writing these reports and as a result,  I've noticed a number of similarities in GSA functionality that our customers are not taking full advantage of. I thought it would be useful to share five of them here in the hopes that more GSA customers might make use of them.

User Feedback - So far, I haven't encountered a customer that is providing really sufficient ways for users to provide input into the search process in their organization. This can be anything from something as simple as a link to the search administrator's email in the footer of the results page, up to a feedback form/survey, or even allowing users the capability to add specific search results for certain keywords in a front end.

The Default Collection - It's considered best practice to never modify the default_collection on your GSA so that there is one collection the contains all indexed content. Unusual support incidents have been known to creep into environments where certain items have been added to the Do Not Crawl patterns on the default_collection. We often see customers modifying the default_collection in the GSA configuration unnecessarily.

Results Biasing - If you are going to use a results biasing policy in your environment to affect the relevancy of search results, always create a second biasing policy that keeps the default settings and assign that to a test front end. This can then be used to compare search results side-by-side against the
biasing policy that you implement on your production front end and make sure that the relevancy impact you were looking for is actually occurring. 

Search Logs and Reports - Search logs should be regularly (at least once per month) exported to allow retention of historical data. While they can be very large, they can be safely compressed without data loss to quite small sizes. GSA customers can analyze their historical search data, as it can provide unique and valuable insight into user behavior. It can also provide valuable clues as to what would make useful KeyMatches, additions to your Query Expansion list, and even new OneBox modules. 

Daily Status Report & Mirroring - while the hardware utilized by Google for the GSA is quite robust and reliable, hardware failures can occur. In addition to ensuring that administrators turn on the Daily Status Report function so that they are notified quickly by email in the event of an issue with the GSA hardware, customers can purchase a second less-expensive "hot backup" license for their GSA. This will allow them to employ mirroring between their search appliances to ensure constant availability to the search system while not adding to their administrative burden. 





 


Written by: Jeff Wolff

Thursday, November 13, 2014

Building a Sencha Touch Wrapper for the Brightcove Video Player

Building a Sencha Touch Wrapper for the Brightcove Video Player

We decided that it would be a good idea to develop a Brightcove Video Player object for future use in our Sencha Touch projects. The Brightcove Video Cloud service allows users to maintain and control the distribution of videos they upload. One aspect of the player that makes it useful for web development is that it will create itself as either a Flash or HTML5 object, depending on what the current browser supports.

The component has two main config properties: the playerKey and the playerID. To get these, make an account at brightcove.com and go to the Publishing tab. If you want to load a video when the player initializes, grab a videoID from the Media tab. Additional functions of the Brightcove API require Player and Experience objects, which can be obtained by calling the getPlayer() and getBrightcoveExperience() methods of this class.

In order to make sure that the API fully loads before anything can be done, the initialization work is done after the container is rendered. This is done in the handler for the 'painted' event:

1:  onBCPPainted: function(element, eOpts) {  
2:      if (typeof(brightcove) == 'undefined') {  
3:         this.setMasked({  
4:          xtype: 'loadmask',  
5:          message: 'Loading Brightcove API...',  
6:          indicator: true  
7:         });  
8:         Ext.Loader.loadScriptFile('http://admin.brightcove.com/js/BrightcoveExperiences.js',  
9:          function() {  
10:           this.setMasked(false);  
11:           this.createPlayer();  
12:          },  
13:          function() {  
14:           alert("Failed to load API");  
15:          },  
16:          this  
17:         );  
18:        } else {  
19:         this.createPlayer();  
20:        }  
21:        bp = this;  
22:    },  

First, this checks if the Brightcove API is already loaded. If it is, it proceeds to call the createPlayer() method. If not, it loads the script file and asynchronously calls createPlayer() after the file has been loaded. In order to show the player within a Sencha Touch container, we had to set its HTML to that of the player object and render it.
1:  createPlayer: function() {  
2:        console.log("SCOPE: ", this);  
3:        onTemplateLoadHandler = function(experienceID) {  
4:         bp.onTemplateLoad(experienceID);  
5:        };  
6:        onTemplateReadyHandler = function(evt) {  
7:         bp.onTemplateReady(evt);  
8:        };  
9:        this.htmlString = ''.concat('<object id="{0}" class="BrightcoveExperience">',  
10:         '<param name="bgcolor" value="{1}\" /><param name="width"',  
11:         'value="{2}" /><param name="height" value="{3}" />',  
12:         '<param name="playerID" value="{4}" />',  
13:         '<param name="playerKey\" value="{5}" />',  
14:         '<param name="isVid\" value="{6}" />',  
15:         '<param name="isUI" value="true" />',  
16:         '<param name="dynamicStreaming" value="{7}" />',  
17:         '<param name="@videoPlayer" value="{8}" />',  
18:         '<param name="includeAPI" value="true" />',  
19:         '<param name="templateLoadHandler" value="onTemplateLoadHandler" />',  
20:         '<param name="templateReadyHandler" value="onTemplateReadyHander" />');  
21:        Ext.Viewport.on('orientationchange', 'onOrientationChange', this, {  
22:         buffer: 50  
23:        });  
24:        this.setMasked({  
25:         xtype: 'loadmask',  
26:         message: 'Loading Video...',  
27:         indicator: true  
28:        });  
29:        this.setHtml(Ext.String.format(this.htmlString,  
30:         this.getPlayerName(),  
31:         this.getPlayerBgcolor(),  
32:         this.element.getWidth(),  
33:         this.element.getHeight(),  
34:         this.getPlayerID(),  
35:         this.getPlayerKey(),  
36:         this.getIsVid(),  
37:         this.getDynamicStreaming(),  
38:         this.getVideoID()));  
39:        brightcove.createExperiences();  
40:        this.setMasked(false);  
41:    },  

There are also declared handler's for when the player is loaded and when it is ready to be used. The onTemplateLoad event fires when the Brightcove player is loaded, and instantiates the brightcoveExperience and player fields. It also adds an event listener (A Brightcove event, not a Sencha Touch event) for when the video is played for the first time. This will be important later. The onTemplateReady function simply logs that the player is ready for use.

1:    onTemplateLoad: function(experienceID) {  
2:        var BCExperience = (brightcove.api.getExperience(experienceID));  
3:        this.setBrightcoveExperience(BCExperience);  
4:        var APIModules = (brightcove.api.modules.APIModules);  
5:        var player = BCExperience.getModule(APIModules.VIDEO_PLAYER);  
6:        this.setPlayer(player);  
7:        this.getPlayer().addEventListener(brightcove.api.events.MediaEvent.BEGIN,  
8:         function() {  
9:          console.log('Event media begin');  
10:          if (typeof bp.currentPosition !== "undefined") {  
11:           player.seek(bp.currentPosition);  
12:          }  
13:         });  
14:    },  
15:    onTemplateReady: function(evt) {  
16:      console.log('template ready');  
17:      console.log("SCOPE: " , this);  
18:    }  

We hit a specific challenge when it came to mobile users rotating the device. We initially thought that re-rendering the player with the new width and height would be enough, but that resulted in the player being completely wiped. Instead, we had to create a parent container for the video player. When the player detects a device rotation, it calls its onOrientationChange method. This grabs the current time of the video (if it has been played) and passes it along to the onParentOrientationChange of the parent container along with the new width and height. The reason it checks if the user is using iOS is because iOS devices launch their video in Quicktime by default, which already supports device rotation with seamless playback.

1:    onOrientationChange: function(scope, newOrientation, width, height, eOpts) {  
2:       this.getPlayer().getIsPlaying(function(currentlyPlaying) {  
3:         if (!Ext.os.is('iOS') || !currentlyPlaying) {  
4:          var currentPosition;  
5:          // debugger;  
6:          this.getPlayer().getVideoPosition(false, function(r) {  
7:           console.log(r);  
8:           bp.getParent().parentOrientationChange({  
9:            currentPosition: r,  
10:            html: Ext.String.format(bp.htmlString,  
11:             bp.getPlayerName(),  
12:             bp.getPlayerBgcolor(),  
13:             width,  
14:             height,  
15:             bp.getPlayerID(),  
16:             bp.getPlayerKey(),  
17:             bp.getIsVid(),  
18:             bp.getDynamicStreaming(),  
19:             bp.getVideoID()),  
20:           });  
21:          });  
22:         }  
23:        });  
24:    },  

The parentOrientationChange method destroys the current video and replaces it with a new player with the updated dimensions. Going back to line 10 of onTemplateLoad, it checks if the current object has a field variable called 'currentPosition', which is only given to it by onOrientationChange. If it is, then it tells the player to start playback at that position. This way, even though we are completely destroying and recreating the video player, users can still resume playback at the point where they rotated the device.

1:    parentOrientationChange: function(bcPlayerConfigs) {  
2:      this.remove(this.down('BCPlayer'),true); 
3:      var newPlayer = Ext.create('MyApp.view.BrightcovePlayer', bcPlayerConfigs);  
4:      this.add(newPlayer);  
5:      brightcove.createExperiences();  
6:    }  

Tuesday, November 4, 2014

CommonSpot Custom Field Craziness!

We were recently tasked by the University of Wisconsin, Eau-Claire to produce a series of advanced custom elements to help their content contributors generate a Pinterest-style image grid.
There are three major components to the element:
  1. An image editor that allows contributors to upload an image and then pan/zoom/crop to a specific image size.
  2. An editor that allows contributors to overlay caption on top of the cropped image.
  3. Output of multiple cropped & captioned images into a responsive, pinterest-style display

Creating the Image Editor Custom Field

Starting from a codebase with limited pan/zoom cropping functionality (http://danielhellier.com/imagecrop/), we refactored the jQuery component to support fixed-sized crop areas as well as implement a bounding box and also tied in a slider component to enable easy-to-use zooming. We also added in the capability to transmit the scaling/cropping coordinates to an application server for server-side processing.

Adding Text Captioning by using a Draggable, Editable <h3> Element

For this project, we needed to give contributors the ability to place a text overlay on the image. The word “and” would need to be automatically converted to uppercase and have a style applied via CSS. To enable the users to choose a color, we used the spectrum plugin for jQuery which generally worked as advertised.
You can make editable <div> segments by simply adding a contenteditable attribute to a block element as illustrated below. The cropped image was simply placed behind the <h3> in the container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="#fqfieldname#-container" style="width: #adjustedWidth#px; height: #adjustedHeight#px">
  <div id="#fqfieldname#-preview" style="border: 1px solid black">
    <h3 class="POAeditablelabel"
            id="edittext-#fqFieldName#" contenteditable
            onFocus="stripAnd(this)">#poaAttribs.text#</h3>
    <img id="#fqfieldname#-image"
      style="width:#stParams.nWidth#px;
      height: #stParams.nHeight#px;"
     <cfif poaAttribs.src is not ""> src="#poaAttribs.src#"</cfif>
        >
  </div>
</div>
We then applied jQuery UI’s draggable plugin to enable the user to drag the editable heading within it’s container:
1
2
3
4
5
6
jQuery('##edittext-#fqFieldName#').draggable({ containment: 'parent', axis: 'y' })
.click(function() {
  jQuery(this).draggable({ disabled: false });
}).dblclick(function() {
  jQuery(this).draggable({ disabled: true });
});



Creating a Pinterest-Style Render Handler


Leveraging the jQuery isotope and jQuery lazyload plugins, Fig Leaf’s developers were able to devise an algorithm that would automatically resize images into a “pinterest”-style image grid in order to minimize the right margin space that typically results from implementing these types of layouts as well as work with the dyanamic text overlays required by the customer.
responsiveimagegrid
responsiveimagegrid
Every time the browser is resized, we dynamically rewrite the image CSS classes and then reinvoke the isotope plugin. The key javascript function, executed on browser resize, is illustrated here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function generateStyles() {
  
 // get pointer to stylesheet          
 var ss = $("#POADynamicStyles");
 var container = $("#POAcontainer");
 var width = container.width();
  
// set minimum width of images to 160, maximum width to 201
 var minWidth = 160, maxWidth = 201, iWidth = 0;
                 
                 
 // figure out the optimal number of cols, given the available space
 for (var numCols=2; numCols<15; numCols++) {
   iWidth = Math.ceil(width / numCols);
   // console.log('cols', numCols, 'width', iWidth);
   if (iWidth <= maxWidth && iWidth >= minWidth) {
    break;
   }
  }
                 
  if (numCols == 15) {
    numCols = 3;
    console.log('auto sizing failed');
  }
             
  // 10px margin around images
  var w1 = Math.floor(width / numCols) - 10;
  var reducePercentage = w1/250;
  var fontSize = reducePercentage * 2.7125;
  var lineHeight = 1;
  var heightOffset = 0;
  heightOffset = (-0.13 * (100 - (reducePercentage * 100)));
                 
  var styles = ''.concat(
    '.item.w2  {width : {1}px;} \n',
    '.item.h2 {height: {1}px;} \n',
    '.item {width : {0}px; height: {0}px;}\n',
    '.item h3 {font-size: {2}rem; line-height: {3}; transform : translate(0px,{4}px)}'
   ).format(w1,w1*2+10,fontSize,lineHeight,heightOffset);
                 
                 
   ss.html(styles);
   initIsotope(w1 + 10);
             
}

Would you like to know more?

Contact Fig Leaf Software’s Professional Services group at info@figleaf.com to discover how we can help you achieve your CMS implementation goals.
Written by: Steve Drucker

About Us

Fig Leaf Software is an award-winning team of imaginative designers, innovative developers, experienced instructors, and insightful strategists.

For over 20 years, we’ve helped a diverse range of clients...

Read More

Contact Us

202-797-7711

Fig Leaf Software

1400 16th Street NW
Suite 450
Washington, DC 20036

info@figleaf.com