Steve B
Steve B

Reputation: 37660

jQuery plugin to apply also on dynamically created elements

I'm writing a jquery plugin that should handle extra information on links to specify open behavior.

For example, I want to supports markup like :

  1. <a href="somewhere" data-openmode="NewWindow" class="openmode" />
  2. <a href="somewhere" data-openmode="Modal" class="openmode" />
  3. <a href="somewhere" class="openmode" /> <!-- Not specified -->

The first should open in a new window, the second in a modal dialog, the third should open with the native behavior (whatever target has been set on the tag).

I would like to create a plugin for this behavior as generic as possible. I've written by now:

(function ($) {
    $.fn.myOpenMode = function () {
        return this.mousedown(function () {
            var $this = $(this);

            var mode = $this.data("openmode");
            var href = this.href;
            var handled = true;
            if (mode) {
                switch (mode) {
                    case "NewWindow":
                    case "1":
                        window.open(href, "_blank");
                        break;
                    case "Dialog":
                    case "2":
                        openAsDialog(href);
                        break;

                    // Actually, there are other options, but I removed them for clarity

                    default:
                        handled = false;
                }
            }
            else {
                handled = false;
            }
            return !handled;
        });
    };
})(jQuery);

This code allows me to call from any page something like:

$(function(){
    $(".openmode").myOpenMode();
});

This is working for statically generated tags. However, my applications may generate markup dynamically (most of time using jsRender, but this does not matters).

But because this behavior is set up once when the javascript file is loaded, it won't take dynamically generated objects.

What should I do to handle my requirement?

  1. I tried to use the on method to monitor load events, but this does not works :

    $(function(){
        $(document).on("load",".openmode", function() { $(this).myOpenMode(); });
    });
    

    I understand this does not works because the "load" event does not bubbles

  2. I was thinking about modifying my plugin to put the "on" inside the plugin, but I don't like this idea because it introduces some out of scope behavior in the plugin

  3. I can also call the plugin each time a dynamic node is created, but it will also introduce dependencies into other parts. My plugin won't be as autonomous as I would like.

Does anyone has a suggestion to handle my requirement, keeping my plugin as isolated as possible?

[edit] This should works with IE8 and later (and ideally with other browsers)

[edit] here is a jsFiddle that illustrate the issue (just click on Add and try to click on the newly created element).

Upvotes: 11

Views: 10272

Answers (5)

edvilme
edvilme

Reputation: 578

I'm late to answer, but hope it helps someone. I had the same problem.

$.fn.plugin = function(){
  //some code
}
$(".element").plugin();            //works fine
$(".elementParent").append(".newElement");
$(".newElement").plugin();         // doesn´t work

It doesn't work because you need to call the plugin again after you added the element, like this

function runPlugin(){
   $.fn.plugin = function(){
      //some code
   }
}
runPlugin();                     //call plugin here
$(".element").plugin();          //works fine
$(".elementParent").append(".newElement");

runPlugin();                     // call plugin here
$(".newElement").plugin();       // works fine

Upvotes: 2

Alnitak
Alnitak

Reputation: 339786

Plugins added to $.fn should only apply to the listed elements, and not any future elements.

You should concentrate on having your plugin provide the mechanism, e.g.:

(function($) {

    $.fn.openmode = function(cmd) {
        cmd = cmd || 'on';

        switch (cmd) {

            case 'click':
                // read props, open windows, etc
                break;

            case 'on':
                this.addClass('openmode');
                break;

            case 'off':
                this.removeClass('openmode');
                break;
         }
    });

})(jQuery);

and then allow the plugin user to register the event handler which triggers that mechanism, using event delegation if necessary:

$(document).on('click', 'a.openmode', function() {
    $(this).openmode('click');
});

The latter code could be put into the jQuery namespace too, as a utility function:

(function($) {
    $.openmode = function(cmd) {
        cmd = cmd || 'on';

        switch (cmd) {

            case 'on':
                $(document).on('click.openmode', 'a.openmode', function() {
                    $(this).openmode('click');
                });
                break;

            case 'off':
                $(document).off('click.openmode', 'a.openmode');
                break;
        }
     };
})(jQuery);

Such that just calling:

$.openmode();

will do all the work required to enable the plugin for every current (and future) .openmode element.

Upvotes: 8

A. Wolff
A. Wolff

Reputation: 74420

You could still implement your own observer method, here extending append method:

;(function ($) {
    var fctsToObserve = {
        append: [$.fn.append, 'self']
    }, fctsObserveKeys = '';
    $.each(fctsToObserve, function (key, element) {
        fctsObserveKeys += "hasChanged." + key + " ";
    });
    var oOn = $.fn.on;
    $.fn.on = function () {
        if (arguments[0].indexOf('hasChanged') != -1) arguments[0] += " " + fctsObserveKeys;
        return oOn.apply(this, arguments);
    };
    $.fn.hasChanged = function (types, data, fn) {
        return this.on(fctsObserveKeys, types, null, data, fn);
    };
    $.extend($, {
        observeMethods: function (namespace) {
            var namespace = namespace ? "." + namespace : "";
            var _len = $.fn.length;
            delete $.fn.length;
            $.each(fctsToObserve, function (key) {
                var _pre = this;
                $.fn[key] = function () { 
                    var target = _pre[1] === 'self' ? this : this.parent(),
                        ret = _pre[0].apply(this, arguments);
                    target.trigger("hasChanged." + key + namespace, arguments);
                    return ret;
                };
            });
            $.fn.length = _len;
        }
    });
    $.observeMethods()
})(jQuery);

Should works in old browsers too, but ya, observers got some performance issue, even you could optimize it.

SEE DEMO

Upvotes: 1

Steve B
Steve B

Reputation: 37660

I found a solution that rely on the on() method, within the plugin.

The plugin is in this case waiting a selector argument to still keep the selector at the application level (not the plugin level) :

(function ($) {
    $.fn.myOpenMode = function (selector) {
        return $(document).on("mousedown", selector ,function () {
            var $this = $(this);

            var mode = $this.data("openmode");
            var href = this.href;
            var handled = true;
            if (mode) {
                switch (mode) {
                    case "NewWindow":
                        alert("NewWindow");
                        break;
                    case "Dialog":
                        alert("Dialog");
                        break;   
                    default:
                        handled = false;
                }
            } else {
                handled = false;
            }
            return !handled;
        });
    };
})(jQuery);

Then, I have to change the call to my plugin like this:

$(function(){
    $.fn.myOpenMode(".openmode");
});

This is working as expected. However, I'm not very confident in respecting jQuery plugin guidelines.

So if anyone has a better solution, I'll be happy to know.

Upvotes: 1

user1796666
user1796666

Reputation:

You can use the so called Mutation Observer approach to react to changes in your DOM. Here's more info about it:

MutationObserver

and here are some good examples:

DOM manipulations

Upvotes: 0

Related Questions