Question About Using jQuery to Solve Interaction Problems

This week’s question comes from Dhiraj Shah.

I want to learn about jQuery. It’s not that I just want to learn the syntax and how it works. Instead I want to be able to learn how to logically solve the problems using jQuery. By problems I mean to say how to think logically whenever some problems come in the way. For example: I have 100 list items (li)s and by default, I am showing just 4 and there’s a button to load more. So, when you click on that button for the first time, it loads 4 more, when you click for the second time, it loads 5 more and each time you click on the button, the list grows by one more than earlier. So, how to logically think about this problem and solve it? I mean, what should be the approach? How to think when these kind of problems come?

Sherpa Chris Casciano answers:

There are two interlocking pieces at the core of this puzzle:

  1. Understanding algorithms, or the abstract steps, needed to solve the problem you’re encountering.
  2. Determining the best implementation of the outlined solution using jQuery conventions and tools.

Both are key to a well-crafted interactive widget.

Understanding Algorithms

On the most basic level, an algorithm is a list of steps to solve a problem from beginning to end. This can be as simple as a cooking recipe (start with certain ingredients, combine them in the proper order, bake, serve). With most coding problems the process is a bit less linear, with decisions, loops and repetition, much like a flow chart.

To design the solution for the interaction problem described in your question, I would look at the problem from three different angles:

  1. Outline the beginning, middle, and end of your typical interaction – any event that might take place while you’re in the middle of the flow.
  2. Identify edge cases, such as what does the first step look like, what does the last step look like, what do you do if there’s an error or you can’t find content?
  3. Identify what data or HTML elements you need to initialize and then manage the interactions.

As I think about the solution, I write the steps out as code comments, which serve as an “outline” for the JavaScript code that will make the interactions happen. Roughly, this process might result in pseudocode comprised of comments:

// find all list widgets on the page and initialize them
     // initialization edge case: don't do anything special if there are 4 or less items

     // step 1. hide all but the first 4 items.
     // step 2. draw a 'more' button at the end of the list.
     // step 3. watch more button for clicks and then attempt to show additional items

// when the more button is clicked
     // inputs: which list widget was clicked, how many times widget has already been clicked
     // step 1. calculate how many items you should display on this iteration
     // step 2. find those next X items to display
     // step 3. reveal these items
     // step 4. update state of list (increment counter, etc) & check the edge cases
          // if there are no more items to display, clean up after ourselves

// when we have no more items to show    
     // step 1: remove the more button
     // step 2: reset or remove any counters or other

This process can quickly identify the structure of the code and help you walk through the process in your head before you think about the implementation of the individual steps in JS or HTML terms.

Implementation in jQuery

Writing the actual code to create the interaction can be broken down into two main parts:

  1. Creating the structure and logical flow for the code to follow.
  2. Implementing the individual steps in the process.

How familiar you are with both JavaScript and jQuery will inform how elegant your solution to the problem will ultimately be. The jQuery API documentation will be your best friend to determine which functions and conventions will help your implementation.

jQuery Selectors and Selector Extensions

For purposes of discussion, let’s state up front that hidden list items [li element] will each be given a class attribute “hide,” which will be styled to hide the item from view. To reveal an item, all you have to do is remove that “hide” class. With this in mind, each time the “more” button is clicked, you need to select the correct number of list items that still have the “hide” class and then remove the class from them.

jQuery has two selectors, :lt() and :gt(), which can assist with the process of grabbing the first X number of items in the list that match the class selector. Using these jQuery selector extensions, you can remove the class from the first 4 items that are still hidden with code that looks like the following:

var $theListBeingChanged = $("ul"), // decide which list we're manipulating
     howManyMore = 4; // decide how many more we are showing
$theListBeingChanged.children(".hide:lt("+(howManyMore-1)+")").removeClass("hide");

Other jQuery Basics

jQuery also has other helpers you can leverage for this interaction. Consider the following HTML structure for a list before and after the interaction is initialized:

Before

ul#longList
     li
     …
     li

After

ul#longList
     li
     li
     li
     …
     li.hide
     li.hide
     …
     li.hide
button.more

To determine if this list has enough items to initialize your widget, or to check if you still have items remaining, you can test the “length” of any selection:

if ($("#longList").children("li").length > 4) { /* have more than 4 items, so initialize the widget */ }
if ($("#longList").children(".hide").length > 0) { /* don't have any more hidden items */ }

You can also use the data attribute to store or retrieve any information you need, such as widget state or counter:

$("#longList").data('listWidgetCounter",1);

And you can use traversing functions to go from the button that was clicked to the previous sibling ordered or unordered list:

$(".more").on("click",function(event) {
     event.preventDefault();
     $associatedList = $(this).prev("ol,li");
     // next step: do something with the newly found $associatedList
},false);

jQuery Plugin Patterns

Beyond the implementation details of the individual steps outlined, there’s also the question of how best to organize your code so that the flow matches the algorithm in a way that makes it easy to reuse the code on other parts of your site or on other projects. jQuery’s plugin architecture allows for just this pattern to follow.

The jQuery Plugin Authoring docs are a good place to start to read up on how plugins work. Borrowing from these docs, the essential plugin wrapper looks like the following, where you attach your newly written plugin to the jQuery object in a manner that lets you later call it as part of any collection of elements:

// define the plugin
(function( $ ){
  $.fn.myPlugin = function() {
      // there's no need to do $(this) because
      // "this" is already a jquery object
      
      // $(this) would be the same as $($('#element'));
      this.fadeIn('normal', function(){
      
        // the this keyword is a DOM element
      
      });
  };
})( jQuery );

// use the plugin
$('#element').myPlugin();

There can be many other working parts for a complete plugin — from event binding, to properly namespacing your methods, to cleanly storing the data the plugin needs. While this can seem like it can quickly turn into a complex maze of code and instances of the “this” keyword, fear not! There are many established and documented plugin patterns you can follow to turn your code into a plugin.

Based on the basic patterns for defining methods described in the jQuery docs, here’s an outline of the start of what your list widget plugin — let’s call it the expandableList plugin — may look like:

(function($){
  var methods = {
    init: function() {

      // for every element we're initilaizing the plugin for do the following
      return this.each(function(){

        var $this = $(this),
            data = $this.data('expandableList');

        // initialization edge case: don't initialize if we already have initialized
        if (!data) {
          // initialization edge case: don't do anything special if there are 4 or less items
          if ($this.children('li').length > 4) {
            // step 1. hide all but the first 4 items.
            // step 2. draw a 'more' button at the end of the list.
            var $more = $('<button class="more">more</button>');
            $this.after($more);

            // step 3. watch more button for clicks and then attempt to show additional items (call showMore)
            $more.on('click',function(event) {
              event.preventDefault();
              methods.showMore.apply($this);
            });

            // step 4. store counter data into this element
            data = {
              'counter': 0
            };
            $this.data('expandableList',data);
          }
        }

      });
    },
    showMore: function() {
      // grab the data we had saved
      var data = this.data('expandableList'),
          counter = data.counter;

      // step 1. calculate how many items you should display on this iteration
      // step 2. find those next X items to display
      // step 3. reveal these items
      // step 4. update state of list (increment counter, etc) & check the edge cases
        // if there are no more items to display, clean up after ourselves (call destroy method)


      // clean up stored data
      data.counter = counter;
      this.data('expandableList',data);

      return this;
    },
    destroy: function() {
      // step 1: remove the more button
      // step 2: reset or remove any counters or other
      this.data('expandableList',null);
      return this;
    }
  };

  $.fn.expandableList = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error('Method '+method+' does not exist on jQuery.expandableList');
    }

  };
})(jQuery);

I’ve filled out some key pieces that might trip you up, but finishing out the plugin behavior is as simple as making the calculations and HTML DOM updates already discussed. Though some of the references or method-calling styles may not be familiar to you, the elements of this plugin match quite well to the parts of our original algorithm.

This is only the start of developing your own jQuery plugin. For more discussion and deep-diving into various jQuery plugin patterns the article Essential jQuery Plugin Patterns by Addy Osmani for Smashing Magazine is a great launching point. The jQuery Boilerplate Project is also a good, more simplified, starting point.