1024px-Flatirons_Winter_Sunrise_edit_2

Configuring jQuery UI widgets often means making sure certain methods are called on a given DOM element (wrapped in jQuery) before usage occurs. We see this configuration pattern everywhere in jQuery UI. Great everyday examples include configuring the datepicker widgets, as well as draggable and droppable interactions. A relatively common example is something like the following:

[js] $(function(){
$(‘input.date:text’).datepicker();
//…
});
[/js]


Just to review, the selector we are using could be translated into words as ‘Select all elements that have a class named date and are also inputs of type text’.

This is functional – except when we get into dynamic additions of elements. As we utilize Ajax to give a richer experience, we run the risk of including a input of type text with the date class on it. This often leads one to start trying to configure UI widgets in the success callback of an Ajax request:

[js] $(function(){
$(‘input.date:text’).datepicker();
//…
});

$.get(‘somePartialServerPage.html’, function(data){
$(‘#ajaxContainer’)
.html(data)
.find(‘input.date:text’)
.datepicker()
.end()
.find(‘.myDraggableClass’)
.draggable({
// config draggable here
});
});
});
[/js]

This could certainly be refactored to be a better solution. We could use named functions to make sure we are DRY, or we could use .ajaxSuccess() to handle adding the behavior with a single global event handler. Yet still the solution is not elegant and could be prone to many wasteful lookups.

This looks like a job for .live(). Attaching behavior to dynamically added elements is the most direct use case for .live(). But with interactions/widgets, often the widgets must be set up before interaction with the element occurs. Live (just like .bind()) presents us with no ways to do this directly.

However, with a little creativity and a good understanding of UI events we can find interesting ways of using live to attach widgets and interactions dynamically to elements.

Scenario 1: Draggable

Draggable interactions are fairly straightforward and familiar to most. When the users presses the mouse down, the draggable behavior will begin until a point where the user release the element by using a mouse up. Thus we must have the draggable behavior configured before the mousedown occurs. Can we use any events on that element to accomplish this task?

In order to be in a position to mousedown, the mouse will first have to fire a few other events. In particular we know that mouseenter and mouseover must fire previous to any mousedown interaction. If we use that event to configure behavior, we can ensure any element is draggable – even those dynamically added!

[js] $(‘.myDraggableClass’).live(‘mouseenter’,function(){
$(this).draggable({
// configure draggable here
});
});
[/js]

Now we must consider that multiple mouse enter events could fire as the user’s mouse exits and re-enters the elements. It would be prudent of us to check to see if the draggable interaction is already configured on that element. Looking under the hood, jQuery draggable add metadata to the element using jQuery’s .data() method. We can use this to our advantage to check and see if the behavior is set up:

[js] $(‘.myDraggableClass’).live(‘mouseenter’,function() {
var $this = $(this);
if(!$this.is(‘:data(draggable)’)) {
$this.draggable({
// configure draggable here
});
}
});
[/js]

Not only does this solution ensure that we will have draggable behavior attached to all elements, but it also scales quite well. If we were to have hundreds of images that were in the dom and supposed to be draggable, sluggish behavior could present itself. Here we only configure draggable interactions on those elements that are realistic candidates for the behavior.

Scenario 2: Datepicker

Datepicker presents a different challenge. While mouseenter will work in some cases, input boxes can gain focus and the datepicker behavior should begin without any mouse interaction. Fortunately we can account for this by adding the datepicker as through the focus in event. On top of that, the datepicker will immediately fire for us.

[js] $(‘input.date:text’).live(‘focusin’, function() {
var $this = $(this);
if(!$this.is(‘:data(datepicker)’)) {
//this will attach a datepicker control & datepicker will immediately fire
$this.datepicker();
}
});
[/js]

We now have datepicker functionality on any appropriate input text element whether it is added at the initial page load or dynamically.

Improving performance with delegate

One possible way to improve performance would be to scope the context of the “live listeners” with the use of .delegate(). With .live() events the way they are written above the event propagation will occur up to the document level before the handlers will be called. With delegate (or scoping live) we can catch the event happening lower in the dom tree. For example let us say that the draggable events will all be occurring on the page within a container element. This is a great candidate to use delegate to achieve the same behavior as above but with better performance. This can also be a great solution where different regions on the page have draggable elements, but in each region you want a different configuration of draggable:

[js] $(‘#myContainer’).delegate(‘.myDraggableClass’, ‘mouseenter’, function() {
var $this = $(this);
if(!$this.is(‘:data(draggable)’)) {
$this.draggable({
// configure draggable here
});
}
});
[/js]

Comments