Friday, March 27, 2015

jQuery eVent dElegation

One of the banes of my existence has been interacting with dynamically created elements within an html/jQuery web application that I've been developing. Lots of html elements need to be created, moved, deleted, updated, etc.  If an element was around when events where attached to a specific group of elements, it misses out and doesn't get to play like the rest of the kids in the playground. It has to just sit there and watch.

Luckily the .on() method in jQuery gives these new elements legs. I swung across a great explanation of event delegation on an extremely reputable, highly authoritative, source called stackoverflow
Case 1 (direct):
$("div#target span.green").on("click", function() {...});
== Hey! I want every span.green inside div#target to listen up: when you get clicked on, do X.
Case 2 (delegated):
$("div#target").on("click", "span.green", function() {...});
== Hey, div#target! When any of your child elements which are "span.green" get clicked, do X with them.
Summary
In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each span is directly responsible for its own events.
In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated.

http://stackoverflow.com/questions/8110934/direct-vs-delegated-jquery-on
The benevolent author here is N3dst4

I really couldn't have said this better myself, that's why I let n3dst4 say it for me. This is not something new for me. But it's still worth repeating. My challenge comes when I need to attach some functionality without having an event to pass to the .on() method. This usually happens when I want to apply a plugin and it's options to the kids at the playground.

Example anyone? ok.
In my web application, I need to make new blocks of elements and make those blocks be droppable in-other-words, I need them to be appropriate receptors for draggable elements(yes I'm cheating and letting jQuery-ui do the stuff they're good at). Turns out, if you have 20-30 elements containing 40-50 elements more elements that you want to make droppable, it becomes unreasonable to run the function that makes all the elements droppable again every time you add one more set of drop-locations(gets really slow). So, how do I delegate the droppable functionality? I had a rather difficult time figuring out that I could simply run the droppable function when adding the new element.

First, I wanted the options to remain consistent and DRY. So I put the options object in a variable.
var droppable_options = {accept: '.some_class', drop: some_function(){...}, ...}

Those options where passed to the elements on initialization.
$('.drop_location').droppable(droppable_options);

Then while adding the new drop locations I was able to make them droppable in the same line of code
$('<div class="drop_location styling_class"></div>').droppable(droppable_options).appenTo('#container_element');

It just seemed to simple to be true. But sure enough, before I knew it, I was "it", the new elements were hiding, I was seeking, everybody was playing nice.

I came away from this with a couple of things:
1. Never target elements that may be coming or going, always target their "anchor" elements and delegate to them.
2. Delegation doesn't work so well with plugin functionality but there is a nice work around.
3. Learn more about event listeners. ( $(element).on('custom_event', do_something(){make myself differnt}) and $(another_element).trigger('custom_event');  ) I'm hoping this will simplify things.

No comments:

Post a Comment