Плагин jQuery для применения также к динамически созданным элементам

Я пишу плагин jquery, который должен обрабатывать дополнительную информацию о ссылках для указания открытого поведения.

Например, я хочу поддерживать разметку наподобие:

  • <a href="somewhere" data-openmode="NewWindow" class="openmode" />
  • <a href="somewhere" data-openmode="Modal" class="openmode" />
  • <a href="somewhere" class="openmode" /> <!-- Not specified -->

Первое должно открыться в новом окне, второе - в модальном диалоге, третье должно открываться с помощью собственного поведения (любая цель была установлена ​​в теге).

Я хотел бы создать плагин для такого поведения как можно более общего. Я уже писал:

(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);

Этот код позволяет мне вызвать с любой страницы что-то вроде:

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

Это работает для статически созданных тегов. Однако мои приложения могут генерировать разметку динамически (большую часть времени используют jsRender, но это не имеет значения).

Но поскольку это поведение настраивается один раз, когда загружается файл javascript, он не будет принимать динамически сгенерированные объекты.

Что мне делать для выполнения моего требования?

  • Я попытался использовать метод on для отслеживания событий загрузки, но это не работает:

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

    Я понимаю, что это не работает, потому что событие "load" не пузырится

  • Я подумывал о модификации моего плагина, чтобы поместить "on" внутри плагина, но мне не нравится эта идея, потому что она вводит некоторые из нестандартного поведения в плагине

  • Я могу также вызывать плагин каждый раз, когда создается динамический node, но он также вводит зависимости в другие части. Мой плагин не будет таким автономным, как хотелось бы.

Есть ли у кого-нибудь предложение для моего требования, сохраняя мой плагин как можно более изолированным?

[edit] Это должно работать с IE8 и более поздними версиями (и в идеале с другими браузерами)

[edit] здесь jsFiddle, которые иллюстрируют проблему (просто нажмите Add и попробуйте нажать на вновь созданный элемент).

Ответ 1

Плагины, добавленные в $.fn, должны применяться только к перечисленным элементам, а не к будущим элементам.

Вам следует сосредоточиться на том, чтобы ваш плагин предоставил механизм, например:

(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);

а затем разрешить пользователю плагина регистрировать обработчик событий, который запускает этот механизм, используя при необходимости делегирование событий:

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

Последний код можно также поместить в пространство имен jQuery, как функцию утилиты:

(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);

Таким образом, чтобы просто вызвать:

$.openmode();

выполнит всю работу, необходимую для включения плагина для каждого текущего (и будущего) элемента .openmode.

Ответ 2

Я нашел решение, которое полагается на метод on(), внутри плагина.

В этом случае плагин ожидает аргумент selector, чтобы сохранить селектор на уровне приложения (а не уровень плагина):

(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);

Затем мне нужно изменить вызов моего плагина следующим образом:

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

Это работает как ожидалось. Тем не менее, я не очень уверен в уважении правил плагина jQuery.

Итак, если у кого-то есть лучшее решение, я буду рад узнать.

Ответ 3

Вы все равно можете реализовать свой собственный метод наблюдателя, расширяя метод добавления:

;(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);

Должен работать и в старых браузерах, но я, наблюдатели, получили некоторые проблемы с производительностью, даже вы могли бы оптимизировать его.

СМОТРЕТЬ ДЕМО

Ответ 4

Я опаздываю, но надеюсь, что это поможет кому-то. У меня такая же проблема.

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

Это не работает, потому что вам нужно снова вызвать плагин после добавления элемента, например

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

Ответ 5

Вы можете использовать так называемый подход Mutation Observer, чтобы реагировать на изменения в DOM. Здесь больше информации об этом:

MutationObserver

и вот несколько хороших примеров:

DOM-манипуляции