Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preferred way of modifying elements that have yet to be created (besides events)

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.

like image 609
scragz Avatar asked Feb 04 '11 02:02

scragz


People also ask

What is SCAMPER method used for?

The SCAMPER Technique is a team brainstorming technique used to develop or improve products or services. SCAMPER is an acronym for Substitute, Combine, Adapt, Modify/Magnify, Purpose, Eliminate/Minimize and Rearrange/Reverse.

What method allows an element to be moved from its current position in CSS?

position: relative; Setting the top, right, bottom, and left properties of a relatively-positioned element will cause it to be adjusted away from its normal position.

What is modify in SCAMPER?

Modify (Also Magnify and Minify) It's time to magnify or exaggerate your idea, product, problem, or process—or to minify it. These questions will give you new insights about which components are the most important ones. Think about changing part or all of the current situation or product.

What is SCAMPER technique with example?

SCAMPER is an acronym and you ask the following types of question when you use this tool: Substitute: What elements of this product or service can we substitute? Combine: How can we combine this with other products or services? Adapt: What idea from elsewhere can we alter or adapt?


2 Answers

In my opinion, the DOM Level 3 events DOMNodeInsertedhelp (which fires only for nodes) and DOMSubtreeModifiedhelp (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

<div id="test">Test DIV</div> 

JS

(function(_append, _appendTo, _after, _insertAfter, _before, _insertBefore) {     $.fn.append = function() {         this.trigger({             type: 'DOMChanged',             newnode: arguments[0]         });         return _append.apply(this, arguments);     };     $.fn.appendTo = function() {         this.trigger({             type: 'DOMChanged',             newnode: this         });         return _appendTo.apply(this, arguments);     };     $.fn.after = function() {         this.trigger({              type: 'DOMChanged',              newnode: arguments[0]          });         return _after.apply(this, arguments);     };      // and so forth  }($.fn.append, $.fn.appendTo, $.fn.after, $.fn.insertAfter, $.fn.before, $.fn.insertBefore));  $('#test').bind('DOMChanged', function(e) {     console.log('New node: ', e.newnode); });  $('#test').after('<span>new span</span>'); $('#test').append('<p>new paragraph</p>'); $('<div>new div</div>').appendTo($('#test')); 

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. .appendChild is located in Element.prototype.appendChild for instance, might be worth a try.

update

I tested overwriting Element.prototype.appendChild etc. 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 interval or timeouts, eeek).

Conclusion

A mixture of DOM Level 3 events where supported and hooked DOMmanip methods is probably the best you can do here.

like image 74
jAndy Avatar answered Oct 05 '22 11:10

jAndy


I was reading up on the new release of jQuery, version 1.5 and I immediately thought of this question.

With jQuery 1.5 you can actually create your own version of jQuery by using something called jQuery.sub();

That way you can actually override the default .append(), insert(), .html(), .. functions in jQuery and create your own custom event called something like "mydomchange" - without it affecting all other scripts.

So you can do something like this (copied from the .sub() documentation with minor mod.):

var sub$ = jQuery.sub(); sub$.fn.insert = function() {     // New functionality: Trigger a domchange event     this.trigger("domchange");     // Be sure to call the original jQuery remove method     return jQuery.fn.insert.apply( this, arguments ); }; 

You would have to do this to all the dom manipulation methods...

jQuery.sub() in the jQuery documention: http://api.jquery.com/jQuery.sub/

like image 44
arnorhs Avatar answered Oct 05 '22 11:10

arnorhs