There are a lot of questions about binding future manipulations to non-existent elements that all end up answered with live/delegate. I am wondering how to run an arbitrary callback (to add a class or trigger a plugin, for example) to all existing elements that match a selector and all future elements that match that same selector that are yet to be created.
It seems that the main functionality of the livequery plugin made it into the core but the other part, attaching arbitrary callbacks got lost along the way somehow.
Another common answer is event delegation but what if one doesn't have access to all of the vendor code that is creating the elements to have it trigger the events?
Here is some real-world code:
// with livequery
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
.livequery(function(){
$(this)
.focus(function(){
$(this).addClass('active');
})
.blur(function(){
$(this).removeClass('active');
})
.addClass('text');
});
// with live
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
.live('focus', function(){
$(this).addClass('active');
})
.live('blur', function(){
$(this).removeClass('active');
});
// now how to add the class to future elements?
// (or apply another plugin or whatever arbitrary non-event thing)
One approach would be to monitor when new nodes are added/removed and re-trigger our selectors. Thanks to @arnorhs we know about the DOMNodeInserted event, which I would ignore the cross-browser problems in the hope that those small IE patches could someday land upstream to jQuery or knowing the jQuery DOM functions could be wrapped.
Even if we could ensure that the DOMNodeInserted fired cross-browser, however, it would be ridiculous to bind to it with multiple selectors. Hundreds of elements can be created at any time, and making potentially dozens of selector calls on each of those elements would crawl.
My best idea so far
Would it maybe be better to monitor DOMNodeInserted/Deleted and/or hook into jQuery's DOM manipulation routines to only set a flag that a "re-init" should happen? Then there could just be a timer that checks that flag every x seconds, only running all those selectors/callbacks when the DOM has actually changed.
That could still be really bad if you were adding/removing elements in great numbers at a fast rate (like with animation or ____). Having to re-parse the DOM once for each saved selector every x seconds could be too intense if x is low, and the interface would appear sluggish if x is high.
Any other novel solutions?
I will add a bounty when it lets me. I have added a bounty for the most novel solution!
Basically what I am getting at is a more aspect-oriented approach to manipulating the DOM. One that can allow that new elements are going to be created in the future, and they should be created with the initial document.ready modifications applied to them as well.
JS has been able to do so much magic lately that I'm hoping it will be obvious.
In my opinion, the DOM Level 3 events
DOMNodeInsertedhelp (which fires only for nodes) andDOMSubtreeModifiedhelp (which fires for virtually any modification, like attribute changes) are your best shot to accomplish that task.Of course, the big downside of those events is, that the Internet Explorers of this world don't support them
(...well, IE9 does).
The other reasonable solution for this problem, is to hook into any method Which can modify the DOM. But then we have to ask, what is our scope here?
Is it just enough to deal with DOM modification methods from a specific library like jQuery? What if for some reason another library is modifying the DOM or even a native method ?
If it's just for jQuery, we don't need
.sub()at all. We could write hooks in the form of:HTML
JS
A live example of the above code can be found here: http://www.jsfiddle.net/RRfTZ/1/
This of course requires a complete list of DOMmanip methods. I'm not sure if you can overwrite native methods like
.appendChild()with this approach..appendChildis located inElement.prototype.appendChildfor instance, might be worth a try.update
I tested overwriting
Element.prototype.appendChildetc. in Chrome, Safari and Firefox (official latest release). Works in Chrome and Safari but not in Firefox!There might be other ways to tackle the requirement. But I can't think of a single approach which is really satisfying, like counting / watching all descendents of a node (which would need an
intervalortimeouts, eeek).Conclusion
A mixture of DOM Level 3 events where supported and hooked DOMmanip methods is probably the best you can do here.