Blog

Configuring UI widgets and interactions with .live() and .delegate()

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:

$(function(){
  $('input.date:text').datepicker();
  //...
});


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:

$(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
        });
  });
});

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!

$('.myDraggableClass').live('mouseenter',function(){
  $(this).draggable({
    // configure draggable here
  });
});

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:

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

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.

  $('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();
    }
  });

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:

$('#myContainer').delegate('.myDraggableClass', 'mouseenter', function() {
  var $this = $(this);
  if(!$this.is(':data(draggable)')) {
    $this.draggable({
      // configure draggable here
    });
  }
});
Tweet about this on TwitterShare on FacebookShare on RedditShare on Google+Share on LinkedIn

  • Pingback: Tweets that mention Configuring UI widgets and interactions with .live() and .delegate() | Enterprise jQuery -- Topsy.com

  • Justin Meyer

    This is a decent technique to offset the initialization costs of a heavy page, but for drag drop, you should just use JavaScriptMVC's delegate-able drag-drop plugin:

    http://jupiterjs.com/news/delegate-able-drag-dr

  • http://dougneiner.com Douglas Neiner

    Hey Justin, thanks for stopping by!

    I think like everything JavaScript developers work with, it depends on the existing environment. If you are already using jQuery UI, then adding a separate drag and drop library for delegation would be overkill. If, on the other hand, drag and drop was the primary thing you working on, the delegate-able drag-drop seems like a great plugin. The obvious caveat is its dependency on the currently unreleased jQuery 1.4.3.

  • Pingback: Tweets that mention Configuring UI widgets and interactions with .live() and .delegate() | Enterprise jQuery -- Topsy.com

  • http://twitter.com/Poetro Peter Galiba

    For optimizing the .date:text you should use input.date:text and the selector would be much faster.

  • jdsharp

    Peter,

    Thanks for catching that, we'll update the post!

  • amWirick

    Definitely right on! Article updated with the faster selector.

  • http://www.codylindley.com/ cody lindley

    Just to clarify (as I was also in need of a clarification), the ':data' selector is available when using the jQuery UI core code (1.6+). Its not avaliable in jQuery 1.4.2.

  • amWirick

    Hello Cody,

    Thanks for the clarification. You are exactly right – jQuery UI only, v1.6+.

    I dig the pseudoselector for data – could make a nice simple plugin.

  • Johans

    Selector ':data' also had me guessing

  • Mikestu

    I'm fairly new to jQuery. What is the advantage of doing “var $this = $(this);” in your example above? I have just been using $(this) directly all the time. Does $(this) involve extra work to get the current object, where your $this stores a reference to it for future use? Thanks!

  • amWirick

    Hello Johans,

    Thanks for the post! Let me clarify some more:
    ':data' is a pseudoselector defined as part of the jQuery UI libraries – you can see the source here – http://github.com/jquery/jquery-ui/blob/master/…. This is a feature that is not (yet) officially documented. It works by checking to see if data exists on the element with the key you passed in. You could achieve a similar selector by explicitly using jQuery's 'data' method with 'datepicker' as the key and looking for data.

    It is important to recognize that this works because jQuery UI is using jQuery's .data method to store data necessary for the UI widgets (which is also something that is not officially documented).

    Please let us know if you still have questions.

  • amWirick

    Hello Mikestu,

    Thank you for stopping by!

    $(this) is could be interpreted as 'take my DOM element and construct a jQuery object which contains a collection of my one element'. If we can minimize this jQuery object construction we can gain some efficiency. Thus I use this line to not repeat the operation:

    var $this = $(this);

    I use the '$' as the first character in the variable name to note to myself that I am dealing with a jQuery object. I then have my reference to my jQuery object stored and can use that rather than going through the working of 'wrapping' my DOM element each time.

  • Mikestu

    Thanks for the explanation! I look forward to your future posts.

  • Cal

    Hi, thank you for this nice technique.

    I've seen in the jQuery UI widget sources that a ':' selector is created for each plugin. So it should be possible to do

    $this.is(':ui-datepicker')

    Haven't tested though.

  • http://dougneiner.com Douglas Neiner

    Hey Cal!

    There is a selector created for each widget, so :ui-datepicker would work, but this article follows the pattern used internally in jQuery UI.

    The difference between :ui-datepicker and :data(datepicker) is the first will error if the UI DatePicker code is not included on the page, and the second will fail silently like most jQuery selectors that don't have a match. It makes the code slightly less coupled, though arguably either selector could probably be used.

  • http://twitter.com/nathansmith Nathan Smith

    Dude. Using mouseenter live wire-up to lazy load draggable functionality is genius.

    Just one suggestion, un-bind the mouseenter event once it's served it's purpose…

    $(foo).live('mouseenter', function() {
    $(this).die('mouseenter').draggable({/* … */});
    });

  • amWirick

    Hello Nathan,

    Great point – true lazy loading with the loader removed after completion. I didn't think of that. Awesome!

    For now we'll consider this the reward people get for reading through the comments :). I'll consider how we can add this back in to the core post – perhaps as a final JS Bin at the end of the post.

  • amWirick

    Hello Nathan,

    Thanks for the comment! Removing die from a single DOM element will not result in any change in behavior by jQuery – http://jsbin.com/agohu4/2/edit

    Interesting though to say the least! It appears that to unbind you must use the same selector as that used with .live()
    Check out these three pieces of jQuery core:
    http://james.padolsey.com/jquery/#v=1.4&fn=jQue
    http://james.padolsey.com/jquery/#v=1.4&fn=jQue
    http://james.padolsey.com/jquery/#v=1.4&fn=_liv

    So with .live() a bind is occuring where the selector is actually being added as part of the namespace of the event. Thus on .die() the namespaced event will only match if the same selector is used.

    Also here is a more optimized version of the above post:
    http://jsbin.com/agohu4/4/edit

  • hm2k

    I had a go at this but with autocomplete and it didn't seem to work…

    Here's my attempt: http://jsfiddle.net/uGdm2/

    Any ideas what I'm doing wrong?

  • http://www.electricprism.com/aeron aglemann

    I like your approach! Especially for existing UI widgets. I took a different approach for crafting widgets to act like delegates:

    https://github.com/aglemann/jq

  • Patrick

    love the idea, trying to use it for droppable and running into some problems, however.

    Works as expected when you mouseover any element, problem is you have to do so before you can drag an element on to it.

    If the mouse enters the droppable element for the first time while dragging a draggable element, it doesn’t seem to trigger the event (or maybe if does after you let go of the draggable, but it’s too late at that point). Any ideas?

    • http://andrewwirick.com/ Andrew Wirick

      Hi Patrick,

      In your situation I would probably initialize the droppable targets on drag start. There is a “dragstart” event hook for draggables – anything that may be a drop target could be initialized in there.

      Check out – http://jqueryui.com/demos/draggable/#event-start

  • http://belugabrain.myopenid.com/ belugabob

    I feel that Nathan meant to say that you should use…
    $('.myDraggableClass').live('mouseenter',function(){    $(this).draggable({        // configure draggable here      });});
    $('.myDraggableClass').live('mouseleave',function(){    $(this).draggable(“destroy”);});

    …as what you want to happen is for the draggable handler to be removed from the element when the mouse leaves it. This prevents the accumulation of multiple elements (potentially, lots) that are marked as draggable – performance degrades when too many elements are draggable.

    There are other issues involved with this, due to the fact that fast drags can cause the mouse to leave the element halfway through a drag operation. I'm working on a solution to this, as I have an application with lots of drag targets, and having them all draggable at once really affect performance. I'll consider posting my solution as and when I get it working.