1

I built this really exciting jQuery plugin which turns an element blue. http://jsfiddle.net/LpCav/

I wish to apply it to elements that are not currently on the page.

I understand that one option is to apply it right after I add the new element to the page. Another option is to clone one of the existing elements and adding that to the page instead of creating a new one. I do not wish to do either of these options.

Instead, can I use on() to apply it to any current or future <li> elements inside <ul id="myNewList">?

If not, can I modify the plugin so it performs this behavior?

Thanks

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
        <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" /> 
        <title>Future</title>  
        <script src="http://code.jquery.com/jquery-latest.js" type="text/javascript"></script> 
        <script type="text/javascript">
            (function( $ ){
                $.fn.makeBlue = function() {  
                    return this.each(function() {
                        $(this).css('color','blue');
                    });
                };
            })( jQuery );

            $(function(){
                $('.myElement').makeBlue();
                $('#add').click(function(){
                    $('#myNewList').html('<li class="myElement">hello</li>')
                });
            });
        </script>
    </head>

    <body>
        <button id="add">Add</button>
        <ul id="myList">
            <li class="myElement">hello</li>
            <li class="myElement">hello</li>
        </ul>
        <ul id="myNewList">
        </ul>
    </body> 
</html> 
user1032531
  • 24,767
  • 68
  • 217
  • 387

3 Answers3

1

Since you discarded the easiest (and probably most common solutions), you are left with catching some "DOM has changed" event and binding to that. From what I have found, there are some events, but are quite poorly supported.. long story short: Here is a library that will give you what you need:

https://github.com/joelpurra/jquery-mutation-summary

Damb
  • 14,410
  • 6
  • 47
  • 49
  • Thanks Dampe, Anything with the name "mutation" seems overly complicated! If need be, I will go back to one of my original two suggested solutions, but wanted to know if others were possible. Now that I am thinking about it, I have never tested the clone solution. I will do so now. – user1032531 Mar 20 '13 at 13:25
  • I also think that this 'hammer' might be too big for your needs, but there's not much left after you skipped the small ones imo :) I personally would go with the first option (apply right after - with some minor tweaks) - or even better.. just try to work with the css classes if possible. – Damb Mar 20 '13 at 13:31
0

I'd usually use $(document).ready(...) block to ensure that the code is called on each page load (even ajax callbacks). You should, though, implement your plugin as an idepotent one - multiple calls on the same element should produce the same result.

From my experience with existing jquery plugins and built-in ones I have observed the following:

  • if the same plugin is called with the same configuration to the same element multiple times, the subsequent calls do not produce any visible differences

  • if the same pluginis called with different configuration on the same element, the effect is of having the configuration merged with the one of the previous calls.

Maybe if you stick to this behavior with your plugins you both will be consistent with the behavior of existing ones and have them work in the scenario with $(document).ready

Using .on(...) might be even better as it will update the corresponding <ul> elements when needed. Still, if there are already list items, the plugin should process them in an idempotent manner.


Edit:

On the idempotence matter in your code it would look something like this:

$.fn.makeBlue = function() {  
    return this.each(function() {
        var elem = $(this);
        if (elem.css('color') != 'blue') {
            // it is likely for the element not to have been processed yet.
            elem.css('color','blue');
        }
    });
};

A better solution would be to add a custom css class to the elements already processed - just to mark them as visited by your plugin. Then you can check with $(this).hasClass('?') instead. This would look like:

$.fn.makeBlue = function() {  
    return this.each(function() {
        var elem = $(this);
        if (!elem.hasClass('makeBlueCalled')) {
            elem.css('color','blue');
            elem.addClass('makeBlueCalled');
        }
    });
};

In your plugin, if the only purpose is coloring the text, I'd better use a css definition

ul.myList > li.myElement.makeBlueCalled { color: blue !important; }

and remove the elem.css('color','blue'); line at all.

Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
  • Thanks Ivaylo. The use of `$(document).ready(function(){` is the same as `$(function(){`. Good suggestion about idempotent. In regards to using `.on(...)`, how would it be done? – user1032531 Mar 20 '13 at 13:54
  • @user1032531, I revisited the post, it is a simple check that does not look very good, but it is all you can get from an html element. – Ivaylo Slavov Mar 20 '13 at 14:14
0

There are DOM2 "mutation events" to catch changes to the DOM tree, but they're not well supported and have in fact been deprecated by W3C. There are new "mutation observer" events but those are in DOM4 and also not fully supported. See this question for more details about those.

In any event (no pun intended) the fact of the matter is that JS is a single threaded language and no element can appear on the page unless you put it there, so it's normally trivial to make the required changes at insertion time.

Otherwise the right answer for this particular example is to put this in your CSS style sheet:

.myElement {
    color: blue;
}

If you're really doing something more complicated than applying a style, the only portable option is to invoke the plugin once you know the element exists.

Community
  • 1
  • 1
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Does not `on()` apply an event to future elements? What is different with applying a plugin? – user1032531 Mar 20 '13 at 13:18
  • With `on`, event is captured by some _existing_ element, then its target is analyzed. It's not the same with effects. – raina77ow Mar 20 '13 at 13:20
  • `.on()` still requires an existing element that actually receives the event, and only works for events that "bubble". – Alnitak Mar 20 '13 at 13:20
  • For instance, `$("#myNewList li").on("click", function(){alert('got me!');});` would place the click() event on future added rows. – user1032531 Mar 20 '13 at 13:21
  • @user1032531 actually, no that wouldn't work - `.on` requires a _second_ selector (between the `"click"` and the callback) which must match the _new element_ - the original `#myNewList li` **must** exist at the time of the call. – Alnitak Mar 20 '13 at 13:24
  • Okay, `$("#myNewList").on("click", 'li', function(){alert('got me!');});` will work on the newly created element. I know it is different than applying a plugin, but just trying to better understand. – user1032531 Mar 20 '13 at 13:43
  • Also, note that the clone solution http://jsfiddle.net/BW8TM/2/ works. Is this not similar to cloning an element with an event tied to it? – user1032531 Mar 20 '13 at 13:45
  • @user1032531 your `.clone()` method _only_ works because the plugin is trivial, and the clone inherits the `style` attribute of the original. If the `makeBlue` method did anything more complicated it wouldn't work. You're also using an undocumented feature of `.html()` by passing an element instead of a string. – Alnitak Mar 20 '13 at 14:04
  • Thanks you Alnitak. I will go with the apply after creation solution. That being said, I am still interested on better understanding what is going on. Any recommended articles on the subject? – user1032531 Mar 20 '13 at 14:12