4
\$\begingroup\$

I am using the following code to lazy load some social buttons in my personal blog. Before continue, I would like to enumerate some dependencies first:

  • jQuery 2.1.4;
  • Using Font Awesome 4.1.0 (but should work with any icon/font);
  • Tested on the most recent Firefox, Chrome, Iceweasel and Safari;

So, the following HTML is using Font Awesome to present the Twitter and Facebook icons. It should be possible to do social actions even when no JavaScript is available, by clicking at the Icons:

<li>
  <a id="tw-root" href="https://twitter.com/share" class="twitter-share-button" data-url="#example" data-text="Example to the CodeReview awesome QA site." data-counturl="#example" data-count="horizontal" data-via="ctwitterc" title="Tweet">
    <span class="fa-stack fa-lg">
          <i class="fa fa-circle fa-stack-2x"></i>
          <i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
        </span>
  </a>
</li>

<li id="fb-root">
  <a id="fb-fa" href="https://www.facebook.com/sharer/sharer.php?u=#example" onclick="window.open('https://www.facebook.com/sharer/sharer.php?u=#example', 'facebook-share','width=580,height=296');return false;"
  title="Share on Facebook">
    <span class="fa-stack fa-lg">
          <i class="fa fa-circle fa-stack-2x"></i>
          <i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
        </span>
  </a>
  <a class="fb-like" data-href="#example" data-layout="button_count" data-action="like" data-share="true" data-text="Example to the CodeReview awesome QA site." data-counturl="#example"></a>
</li>

Here is a snippet (Facebook button will not load, maybe due security protocols, SecurityError: The operation is insecure., you can still check the live site).

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" />
<li>
  <a id="tw-root" href="https://twitter.com/share" class="twitter-share-button" data-url="#example" data-text="Example to the CodeReview awesome QA site." data-counturl="#example" data-count="horizontal" data-via="ctwitterc" title="Tweet">
    <span class="fa-stack fa-lg">
          <i class="fa fa-circle fa-stack-2x"></i>
          <i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
        </span>
  </a>
</li>

<li id="fb-root">
  <a id="fb-fa" href="https://www.facebook.com/sharer/sharer.php?u=#example" onclick="window.open('https://www.facebook.com/sharer/sharer.php?u=#example', 'facebook-share','width=580,height=296');return false;"
  title="Share on Facebook">
    <span class="fa-stack fa-lg">
          <i class="fa fa-circle fa-stack-2x"></i>
          <i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
        </span>
  </a>
  <a class="fb-like" data-href="#example" data-layout="button_count" data-action="like" data-share="true" data-text="Example to the CodeReview awesome QA site." data-counturl="#example"></a>
</li>

<!-- Lazy load -->
<!-- Twitter JavaScript
        http://www.paulund.co.uk/lazy-load-social-media
        -->
<script type="text/javascript">
  var tweetHover = function(e) {
    $(e).hover(
      function() { // mouse enter
        if ($(this).hasClass("share-enabled")) {
          // do nothing
        } else {
          if (typeof(twttr) != 'undefined') { // will load just the entered #tw-root
            twttr.widgets.load(this);
            $(this).addClass("share-enabled");
          } else { // will load all #tw-root, and it sucks
            $.getScript('http://platform.twitter.com/widgets.js');
            $(this).addClass("share-enabled");
          }
        }
      },
      function() { // mouse leave

      }
    );
  };
  $(document).ready(function() {
    tweetHover("#tw-root");
  });
</script>

<!-- Facebook JavaScript
        https://developers.facebook.com/docs/javascript/howto/jquery/v2.4
        -->
<script type="text/javascript">
  var facebook_sdk = '//connect.facebook.net/en_US/sdk.js';
  if (document.documentElement.lang = 'pt-BR') {
    facebook_sdk = '//connect.facebook.net/pt_BR/sdk.js';
  };

  var facebookHover = function(e) {
    $(e).hover(
      function() { // mouse enter
        if ($(this).hasClass("share-enabled")) {
          // do nothing
        } else {
          if (typeof(FB) != 'undefined') { // will load just the entered #fb-root
            FB.XFBML.parse(this);
            $(this).addClass("share-enabled");
            $(this).find("#fb-fa").remove();
          } else { // will load all #fb-root, and it sucks too
            $.getScript(facebook_sdk, function() {
              FB.init({
                appId: 'app-id-goes-here',
                xfbml: true,
                version: 'v2.4'
              });
            });
            $(this).addClass("share-enabled");
            $(document).find("[id=fb-fa]").each(function() {
              $(this).remove()
            });
          }
        }
      },
      function() { // mouse leave
        //
      }
    );
  };
  $(document).ready(function() {
    facebookHover("#fb-root");
  });
</script>

Here is the link of my personal blog to a live example. That code should produce this result:

enter image description here

The following jQuery/JavaScript (if available) will:

    1. load the Twitter (or the Facebook) SDK on mouse enter.
      • If the corresponding SDK is being loaded for the first time, the page might have multiple button IDs at the same time (#fb-root or #tw-root), if so it will load all buttons at once, no matter what button was entered.
    1. If the corresponding SDK was loaded and more buttons IDs appeared somehow (from ajax calls, for example), only the entered button will be loaded.
    1. In both cases:
      • The Twitter SDK will remove its Font Awesome Icon automatically after loaded.
      • The Facebook SDK will not remove its Font Awesome Icon, but I explicitly coded to do so (yes, the id="fb-fa" related stuff is a hack).
      • The buttons should load like this:

    enter image description here

    1. On touch devices, let's consider untested on touch devices (but the buttons should load too).

// Twitter JavaScript
// Inspiration source.
// http://www.paulund.co.uk/lazy-load-social-media
  var tweetHover = function(e){
    $(e).hover(
      function(){ // mouse enter
        if ($(this).hasClass("share-enabled")) {
          // do nothing
        } else {
          if (typeof (twttr) != 'undefined'){ // will load just the entered #tw-root
            twttr.widgets.load(this);
            $(this).addClass("share-enabled");
          } else {  // will load all #tw-root, and it sucks
            $.getScript('http://platform.twitter.com/widgets.js');
            $(this).addClass("share-enabled");
          }
        }
      },
      function(){ // mouse leave

      }
    );           
  };
  $(document).ready(function(){
    tweetHover("#tw-root");
  });

// Facebook JavaScript
// https://developers.facebook.com/docs/javascript/howto/jquery/v2.4

  // some multilanguage support
  var facebook_sdk = '//connect.facebook.net/en_US/sdk.js';
  if (document.documentElement.lang = 'pt-BR') {
    facebook_sdk = '//connect.facebook.net/pt_BR/sdk.js';
  };

  var facebookHover = function(e){
    $(e).hover(
      function(){ // mouse enter
        if ($(this).hasClass("share-enabled")) {
          // do nothing
        } else {
          if (typeof (FB) != 'undefined') { // will load just the entered #fb-root
            FB.XFBML.parse(this); 
            $(this).addClass("share-enabled");
            $(this).find("#fb-fa").remove();
          } else { // will load all #fb-root, and it sucks too
            $.getScript(facebook_sdk, function(){
              FB.init({
                appId   : 'facebook-id-goes-here',
                xfbml   : true,
                version : 'v2.4'
              });
            });
            $(this).addClass("share-enabled");
            $(document).find("[id=fb-fa]").each(function(){$(this).remove()});
          }
        }
      },
      function(){ // mouse leave
        //
      }
    );
  };
  $(document).ready(function(){
    facebookHover("#fb-root");
  });

I would appreciate any comments about the code style and really want to know if you have a better alternative in mind to do the same thing (or better, as you wish).

\$\endgroup\$
  • 1
    \$\begingroup\$ Feel free to add an on-site executable snippet (edit and Ctrl+M) \$\endgroup\$ – Mathieu Guindon Sep 22 '15 at 5:06
  • \$\begingroup\$ @Mat'sMug, done. \$\endgroup\$ – cpicanco Sep 22 '15 at 6:45
2
\$\begingroup\$

Notes on the code style:

  • Avoid deep nesting
  • Split high level logic and low level logic. This let's you see what a function does at a glance, and then dig deeper only if you need to. Code written this way is easier to understand. It also allows you to name the chunks of code with function names, rather than relying on comments.
  • This splitting can be done fractally, at all levels. Think of a book organized into sections, sub-sections, and perhaps sub-sub-sections.
  • Use function fnName() {} rather than var fnName = function() {} unless you have a good reason not to. It's shorter, and allows you to take advantage of js's nice "hoisting" feature, whereby you can define a function after you use it. This lets you put implementation details below high level logic.

As for the general approach, I'm not sure you need to do lazy loading. If you think the social buttons are important for you site, consider loading them as soon as the page loads. I know you are trying to respect your user's bandwidth, but I think the UX is worse with the lazy loading, because of the stutter and the button transformations.

It's been a while since I did FB integration, but I don't remember having to load such a large SDK. You might see if there's a lighter weight alternative. But I could be wrong on this point.

Below is a rewrite (untested) based on the principles above.

function loadFacebookWhenHovering(e) {

  var self = $(this);

  // The highest level of work.
  // Everything this function does is here to see in a single line
  // It adds a hover handler to the given element
  // Want to know the details of the handler?  Keep reading down...
  $(e).hover(mouseEnterCallback, function(){});

  function mouseEnterCallback() { // mouse enter

    // use a guard clause instead of an empty if clause
    if (self.hasClass("share-enabled"))
      return;

    // These 2 lines contain all the "high level" logic
    // of the mouse enter callback
    var isFBLoaded = (typeof (FB) != 'undefined');
    isFBLoaded ? enableWhenFBIsLoaded() : loadFBAndEnable();
  }

  // These helper functions implement the high level logic for the callback
  function loadFBAndEnable() {
    $.getScript(facebook_sdk, function(){
      FB.init({
        appId   : 'facebook-id-goes-here',
        xfbml   : true,
        version : 'v2.4'
      });
    });
    self.addClass("share-enabled");
    $(document).find("[id=fb-fa]").each(function(){self.remove()});
  }

  function enableWhenFBIsLoaded() {
    FB.XFBML.parse(self[0]); 
    self.addClass("share-enabled");
    self.find("#fb-fa").remove();
  }

};
\$\endgroup\$
  • \$\begingroup\$ Why do you think the UX is worse with lazy loading? The presented code can be seen as just the beginning or the core of a more fancy and elegant future implemetation (for example compressor.io). \$\endgroup\$ – cpicanco Sep 23 '15 at 3:36
  • \$\begingroup\$ It's not necessarily worse, but there are a lot of details to get right. Eg, even on the compressor site, where they've put a lot of work into this, I experienced a second or two delay and stutter before the twitter buttons were fully loaded. It gives it a janky feel. At the end of the day, you do a ton of work to test this cross browser, etc, and sure, it's kinda slick, but it hasn't really bought you much. My personal preference is for simplicity. But if you're just doing it fun and learning, go for it! \$\endgroup\$ – Jonah Sep 23 '15 at 3:41
  • \$\begingroup\$ btw, to clarify, i have no problem with pretty minimalist buttons that transform into the uglier function buttons provided by FB, I just don't like the experience of having to wait while they load, and the stutter that occurs. At the very least, if you want to lazy load, you should have a nice looking ajax loader to indicate that's what happening, and ideally a smooth CSS transition into the buttons when they are loaded. Again, lots of details to get right... \$\endgroup\$ – Jonah Sep 23 '15 at 3:48
1
\$\begingroup\$

Here is a reviewed and tested solution based on @Johah's answer. As mentioned above, the Facebook button will not load due to security protocols.

function onHoverFacebook(e) {

  $(e).hover(mouseEnterCallback, mouseLeaveCallback);

  function mouseEnterCallback() {

    // guard clause
    if ($(this).hasClass('share-enabled'))
      return;

    // check if the sdk is loaded
    var isFBLoaded = (typeof (FB) != 'undefined');

    // ? is a ternary conditional operator
    isFBLoaded ? parseSingleButton() : initFBandAllButtons();
  }

  // True condition
  function parseSingleButton() {
    FB.XFBML.parse($(this)[0]); 
    $(this).addClass('share-enabled');
    $(this).find('#fb-fa').remove();
  }

  // False condition
  function initFBandAllButtons() {

    var facebook_sdk = '//connect.facebook.net/en_US/sdk.js';
    
    switch (document.documentElement.lang) {
    case 'pt-BR':
        facebook_sdk = '//connect.facebook.net/pt_BR/sdk.js';
        break;
    case 'en-GB':
        facebook_sdk = '//connect.facebook.net/en_GB/sdk.js';
        break;
    };

    $.getScript(facebook_sdk, function(){
      FB.init({
        appId   : 'facebook-id-goes-here',
        xfbml   : true,
        version : 'v2.4'
      });
    });
    $(this).addClass('share-enabled');
    $(document).find('[id=fb-fa]').each(function(){$(this).remove()});
  }

  function mouseLeaveCallback() {

    // do nothing for now

  }
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" />

<li id="fb-root">
  <a id="fb-fa" href="https://www.facebook.com/sharer/sharer.php?u=#example" onclick="window.open('https://www.facebook.com/sharer/sharer.php?u=#example', 'facebook-share','width=580,height=296');return false;"
  title="Share on Facebook">
    <span class="fa-stack fa-lg">
          <i class="fa fa-circle fa-stack-2x"></i>
          <i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
        </span>
  </a>
  <a class="fb-like" data-href="#example" data-layout="button_count" data-action="like" data-share="true" data-text="Example to the CodeReview awesome QA site." data-counturl="#example"></a>
</li>

<!-- Lazy load -->
<script type="text/javascript">
  $(document).ready(function() {
    onHoverFacebook("#fb-root");
  })
</script>

\$\endgroup\$
  • \$\begingroup\$ Thanks for sharing. You should also consider replacing your switch with a one or two liner like facebook_sdk = '//connect.facebook.net/' + document.documentElement.lang + '/sdk.js'; (untested) \$\endgroup\$ – Jonah Sep 24 '15 at 20:44
  • \$\begingroup\$ Facebook uses '_' (underline) and document.documentElement.lang uses hífens. That would be a problem. \$\endgroup\$ – cpicanco Sep 24 '15 at 21:52
  • 1
    \$\begingroup\$ var lang = document.documentElement.lang.replace(/-/g,'_'); facebook_sdk = '//connect.facebook.net/' + lang + '/sdk.js'; \$\endgroup\$ – Jonah Sep 24 '15 at 22:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.