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

Follow us on LinkedIn for our latest data and tips!

, , ,

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

<div class=”note”>

Note: This is part two in a two part series on creating your first plugin. You can find part one <a href=”https://enterprisejquery.com/2010/07/create-your-first-jquery-plugin-part-1-transition-from-everyday-jquery-code-to-base-plugin/”>here</a>. In the series we’re creating a plugin to handle displaying, and also queuing a series of a messages.

</div>

<h2>Plugin Enhancements</h2>

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.

<h3>Utilizing jQuery Queues</h3>

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. <code>.queue</code> is utilized to add items to the queue. It can also be used to determine the number of items currently in a given queue. <code>.dequeue</code> is used to kick off our queue, or at any point move to the next callback in the queue. <!–more–>

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 <code>.queue</code> and <code>.dequeue</code> methods. Internally, jQuery uses a queue on each element with a key <code>fx</code> for animations. You can attach callback functions to this queue as well.

These two JS Bin links (<a href=”https://jsbin.com/ofeya3/edit”>one</a> and <a href=”https://jsbin.com/iyuri3/2/edit”>two</a>) provide an introduction to queues and an example of queueing your own custom callbacks in the animation queue. The <a href=”https://jsbin.com/ofeya3/edit”>first example</a> provides proof of the queueing functionality and shows a typical mistake that is made when combining queued and non-queued operations. <a href=”https://jsbin.com/iyuri3/2/edit”>Example two</a> 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 <code>fx</code> queue) you must explicitly move the queue to the next callback through the use of <code>.dequeue</code> 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(&quot;myFlashMessage&quot;, 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);

<a href=”https://jsbin.com/abide3/138/edit”>Live JS Bin Demo</a>

A few important features to note in the enhancement:

<ul>
<li>We’ve put all of our functionality for showing the message inside of a <code>.queue</code> 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 <code>.queue</code> method will implicitly iterate over our jQuery collection, and the keyword <code>this</code> will now be set to a single DOM element for each function callback.</li>
<li>With <code>.queue</code> 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 <code>next</code>. Using this function to move to the next callback in the queue is an alternative to explicitly using <code>.dequeue</code>. 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.</li>
<li>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.</li>
</ul>

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:

<ul>
<li>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.</li>
<li>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.</li>
</ul>

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.

<h3>Automatically Closing the Message After a Timeout</h3>

A common scenario in client-side development when you want to force an event to fire <em>even</em> 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 <code>setTimeout</code> function, which will allow us to run a callback after an interval of of time. jQuery provides us with the awesome power of <code>.trigger</code>, 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 <code>setTimeout</code> 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);

<a href=”https://jsbin.com/abide3/144/edit”>Live JS Bin Demo</a>

Two important points to make in this example:

<h4>jQuery’s .trigger method is just cool</h4>

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.

<h4>Using .data for item metadata</h4>

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

This snippet from line thirty-two is huge for understanding a key concept in JavaScript: <code>$this.data(‘messageId’) == messageId</code>

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

If you find yourself using <code>setTimeout</code> in bunches, consider using a plugin such as Ben Alman’s <a href=”https://benalman.com/projects/jquery-dotimeout-plugin/”>doTimeout</a>.

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.