Blog

Create Your First jQuery Plugin Part 2: Plugin Enhancements with .queue and .trigger

Note: This is part two in a two part series on creating your first plugin. You can find part one here. In the series we’re creating a plugin to handle displaying, and also queuing a series of a messages.

Plugin Enhancements

The plugin we worked on in Part 1 focused on getting from a simple jQuery snippet to a reusable and scalable plugin. This post will give us an opportunity to look at two popular techniques that can be used in a variety of situations. We’ll introduce these techniques by adding two enhancements to our code. We will enable the queueing of messages for the message center, and will add features to automatically close the message after a certain time period.

Utilizing jQuery Queues

jQuery queues provide an explicit mechanism for running synchronous operations anywhere they might be necessary. Our current plugin is a great fit for queues. If a previous message is being displayed we would prefer that the new message be queued and displayed synchronously afterwards.

The queue portion of the API contains two methods that are used most of the time. .queue is utilized to add items to the queue. It can also be used to determine the number of items currently in a given queue. .dequeue is used to kick off our queue, or at any point move to the next callback in the queue.

DOM elements can have an arbitrary number of queues attached to them. Each queue can be identified by using a queue name when using the .queue and .dequeue methods. Internally, jQuery uses a queue on each element with a key fx for animations. You can attach callback functions to this queue as well.

These two JS Bin links (one and two) provide an introduction to queues and an example of queueing your own custom callbacks in the animation queue. The first example provides proof of the queueing functionality and shows a typical mistake that is made when combining queued and non-queued operations. Example two shows leveraging the animation queue with a custom callback to provide more desirable behavior.

It is important to recognize that the developer is directly responsible for controlling the dequeueing of callbacks. When dealing with a custom queue (a queue that is not the fx queue) you must explicitly move the queue to the next callback through the use of .dequeue or another mechanism that we will see in the example.

jQuery uses queueing internally to accomplish animations in a synchronous fashion.

So here’s what our new code looks like, enhanced with queueing:

function ($, undefined) {
  $.fn.myFlashMessage = function (params) {
    // Queue the message for view
    this.queue("myFlashMessage", function (next) {
      // Use extend to merge input parameters and defaults.
      // Use an empty object literal first as it is augmented
      // by the extend method.
      var settings = $.extend(
          {},
          {
            levelClass : 'info',
            animationSpeed : 1000
          },
          params);

<pre><code>  if (typeof(settings.message) === 'string') {
    // Now that we are using the jQuery queue method
    // the `this` keyword refers to a single DOM element.
    $(this)
      // Replace html with the message
      .html(settings.message)

      // Add the  appropriate class for the message level
      .addClass(settings.levelClass)

      // Click to close the message.
      // remove the handler once it has run.
      .one(&amp;quot;click&amp;quot;, function () {
        $(this)
          .slideUp(settings.animationSpeed, function(){
            // This will remove the class once slideUp is complete
            $(this).removeClass(settings.levelClass);

            // Next is a function passed in as a parameter
            // to our callback for queue. We can use it to move
            // to the next item in the queue if there is a
            // next function to execute.
            next();
          });
      })
      .slideDown(settings.animationSpeed);
  }
});

// Check the length of the queue
// if the queue length is 1 and the queue
// is hidden, we need to kick off the queue
if (this.queue(&amp;quot;myFlashMessage&amp;quot;).length == 1 &amp;amp;&amp;amp; this.is(&amp;quot;:hidden&amp;quot;)) {
  this.dequeue(&amp;quot;myFlashMessage&amp;quot;);
}
</code></pre>

}
})(jQuery);

Live JS Bin Demo

A few important features to note in the enhancement:

  • We’ve put all of our functionality for showing the message inside of a .queue method call. Because our functionality resides within a jQuery method callback function, the value of the keyword ‘this’ has changed in our next context. The .queue method will implicitly iterate over our jQuery collection, and the keyword this will now be set to a single DOM element for each function callback.
  • With .queue the first argument passed in is a function. This function can be used to move to the next callback in the queue by executing the function. It is conventional to call the function next. Using this function to move to the next callback in the queue is an alternative to explicitly using .dequeue. This function also has the distinct advantage of the fact that that we don’t need to know the queue name. In certain cases you may create a callback function that doesn’t know the name of the queue it will be utilizing. In this case, using the first argument to move to the next callback in the queue might be your only option.
  • We have some logic to determine whether we need to jump start the queue. In this case, we can look to see if the message center is hidden and there is only one callback in the queue (the one we just added), then we will need to start (or restart) dequeueing callbacks.

There are a few refactors/enhancements that would be good for the example. I encourage you to take the JS Bin example and try the following:

  • Make the plugin more DRY by moving the queue name into a single spot. You could use an explicit variable, or perhaps add on the queue name as a parameter.
  • This one’s a bit more challenging: if the queue is already showing, don’t slide up and then down as a transition. Instead do a quick fade out/fade transition.

Keep a queue pattern in mind when looking for solutions to your client-side problems. Other situations may crop up where synchronous operations are paramount. One great example seen in enterprise situations is the need to queue multiple Ajax requests in a certain order to perform a single operation; queues handle this task gracefully.

Automatically Closing the Message After a Timeout

A common scenario in client-side development when you want to force an event to fire even if the user has not triggered the event yet. In our case, we want to hide the message after a timeout period even if the user has not clicked on the message. JavaScript provides us with a setTimeout function, which will allow us to run a callback after an interval of of time. jQuery provides us with the awesome power of .trigger, which we can use to force our click event to fire.

One extra thing to consider: when our callback fires after the timeout, the user may have already progressed to a new message. We don’t want our callback triggering that message to close. We can solve this problem by storing a unique message id when we go to display our message. We can then look at this message id in our setTimeout callback function to determine if the user is still on the same message.

Our solution:

(function ($, undefined) {
  var queueName = 'myFlashMessage';

$.fn.myFlashMessage = function (params) {
    // Queue the message for view
    this.queue(queueName, function (next) {
      // Use extend to merge input parameters and defaults.
      // Use an empty object literal first as it is augmented
      // by the extend method.
      var settings = $.extend(
          {},
          {
            levelClass : 'info',
            animationSpeed : 1000,
            timeout : 3000
          },
          params),
          messageId = String(+new Date);

<pre><code>  if (typeof(settings.message) === 'string') {
    // Don't repeat initializing a jQuery object
    // we can initialize once since in all methods below
    // the keyword 'this' refers to the same
    // DOM element
    var $this = $(this);

    // Set function to run and close on setTimeout if not already
    setTimeout(function () {
      // We need to make sure that the animation hasn't started,
      // the message isn't hidden, and that the message shown
      // is still the message we intend to close.
      if (!$this.is(&amp;quot;:animated,:hidden&amp;quot;) &amp;amp;&amp;amp;
        $this.data(&amp;quot;messageId&amp;quot;) == messageId) {
        // Now this is just cool.
        $this.trigger(&amp;quot;click&amp;quot;);
      }
    }, settings.timeout);

    $this
      // Add a messageId as metadata on the element
      .data(&amp;quot;messageId&amp;quot;,messageId)
      // Replace html with the message
      .html(settings.message)
      // Add the  appropriate class for the message level
      .addClass(settings.levelClass)
      // Click to close the message.  remove the handler once it has run.
      .one(&amp;quot;click&amp;quot;, function () {
        $this
          .slideUp(settings.animationSpeed, function () {
            // This will remove the class once slideUp is complete
            $this.removeClass(settings.levelClass);

            // Next is a function passed in as a parameter to
            // our callback for queue. We can use it to move to
            // the next item in the queue if there is a next
            // function to execute.
            next();
          });
     })
    .slideDown(settings.animationSpeed);
  }
});
// If there are no other messages and the message center
// is hidden, then we need to kick off the queue.
if (this.queue(queueName).length === 1 &amp;amp;&amp;amp; this.is(&amp;quot;:hidden&amp;quot;)) {
  this.dequeue(queueName);
}
</code></pre>

}
})(jQuery);

Live JS Bin Demo

Two important points to make in this example:

jQuery’s .trigger method is just cool

It is common to see a pattern of setting a callback to run after an interval, and then triggering an event. Since we’re dealing with the client-side, triggering an event in code is possible. jQuery makes it dead simple.

Using .data for item metadata

In our example we store each message id to messageId, passing in a new timestamp as our unique id. If you look into the documentation for .data you’ll notice that by using the same key every time of messageId, we’re overwriting the previous messageId stored by the last queued callback.

This snippet from line thirty-two is huge for understanding a key concept in JavaScript: $this.data('messageId') == messageId

In JavaScript functions have access to the context in which they were created. In our case, when we created our setTimeout callback, the context included access to messageId, which is the message identifier for the message we were currently queueing up. When the setTimeout callback is executed, we compare that value against $this.data('messageId') which is the currently displayed message identifier.

If you find yourself using setTimeout in bunches, consider using a plugin such as Ben Alman’s doTimeout.

And there we have it! A modified and scalable plugin. Feel free to extend the plugin as you please, and comment with some JS Bin examples if you find anything interesting or have questions.

Tweet about this on TwitterShare on FacebookShare on RedditShare on Google+Share on LinkedIn