It’s super-common in front-end development to load a lot of content dynamically via AJAX. It’s tempting to rely on jQuery selectors to initialize these elements. We’ve all seen it: a huge chunk of code with twenty anonymous functions, each one a callback to a jQuery .each() method. We like jQuery selector callbacks because they’re convenient–no code is executed unless a match is found. It’s a built-in if-statement! But querying the DOM is an expensive operation which can slow down the loading of your site and drive away users. It’s also lazy coding.

To counter this, consider offloading initializations to a single, generic callback that will minimize the performance hit while retaining the convenience of selector-based initialization. That’s a mouthful! In other words, let’s create a master function to call all the others for us–a MapReduce() for the front-end, if you will. Here’s how.

Example Scenario

Let’s say we’re making a page with two dynamic blocks of content: Recent Tweets and Upcoming Events.

Normally, the static page markup might look something like this:

<div class="recent-tweets"></div>
<div class="upcoming-events"></div>

And the JavaScript:

$(document).ready(function(){
  $('.recent-tweets').each(function(){
    // load some ajax or something
  });

  $('.upcoming-events').each(function(){
    // load some ajax or something
  }
});

So we have two selectors, recent-tweets and upcoming-events, that essentially do the same thing–query the DOM tree for an element with the given class name and, if found, perform the specified function. Not a big deal when dealing with only two selectors as in this example, but as we add more and more dynamic blocks the performance hit adds up quickly, especially on low-powered devices like smartphones.

What if we could boil this down to a single selector? Like so:

<div class="recent-tweets init"></div>
<div class="upcoming-events init"></div>

Then we could initialize both blocks with $('.init').each(). That would be cool! But we need some way to tell the function what code to run on each block. Enter the HTML5 data- attribute:

<div class="recent-tweets init" data-init="loadRecentTweets"></div>
<div class="upcoming-events init" data-init="loadUpcomingEvents"></div>

We’ve created a data-init attribute which contains a unique identifier for each block. In our JavaScript, we use those identifiers as the names of functions. Then we replace both selectors with a single, master selector. Observe:

function loadRecentTweets(theElement) {
  // load recent tweets from the local REST API
  $.ajax('api/tweets').done(function(response){
    // display the data inside the container element
    $(theElement).append(response.content);
  });
}

function loadUpcomingEvents(theElement) {
  // load upcoming events from the local REST API
  $.ajax('api/events').done(function(response){
    // display the data inside the container element
    $(theElement).append(response.content);
  });
}

// master selector
$(document).ready(function(){
  $('.init').each(function(){
    var initFunction = $(this).data('init');

    // this funny-looking line calls the value of \`data-init\` as
    // a function, and passes the element's DOM node as an argument.
    window[initFunction](this);
  });
});

This is great, for several reasons:

  • Readability - A cursory glance at the HTML markup tells us exactly which function affects it.
  • Portability - This code can easily be moved or copied elsewhere without any concern about side-effects.
  • Abstraction - We’ve minimized repetition, so changes are easier to make.

But those two functions still look awfully similar. It would be nice to combine them into something more generic…

Putting data- to Work

Since our master selector callback passes the DOM node itself as an argument, our init functions can read in whatever attributes we want. That means we can do something like this:

function loadTheCode(theElement) {
  var ajaxUrl = $(theElement).data('url');

  // load some data from the specified URL
  $.ajax(ajaxUrl).done(function(response){
    // display the data inside the container element
    $(theElement).append(response.content);
  });
}

Now our markup would look like this:

<div class="recent-tweets init" data-init="loadTheCode" data-url="http://my.api/tweets"></div>
<div class="upcoming-events init" data-init="loadTheCode" data-url="http://my.api/events"></div>

This is getting awesomer. Later, when we decide we want a list of only certain tweets on a different page, all we have to do is add one line of markup:

<div class="recent-tweets init" data-init="loadTheCode" data-url="http://my.api/tweets?search=chocolate"></div>

We just made more AJAX happen without even touching our JavaScript!

Infinite Possibilities

Using data- attributes, we can pass our loader function as many “arguments” as we want. How about data-refresh to specify an auto-refresh interval, or data-cache to control AJAX caching options? Together with a well-tuned REST API this method is virtually limitless.