【问题标题】:jQuery plugin to apply also on dynamically created elementsjQuery 插件也适用于动态创建的元素
【发布时间】:2013-05-14 12:00:20
【问题描述】:

我正在编写一个 jquery 插件,它应该处理有关链接的额外信息以指定打开行为。

例如,我想支持这样的标记:

  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 -->

第一个应该在新窗口中打开,第二个应该在模式对话框中打开,第三个应该以本机行为打开(无论标签上设置了什么目标)。

我想为这种行为创建一个尽可能通用的插件。我现在已经写了:

(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 文件时设置的,它不会采用动态生成的对象。

我应该如何处理我的需求?

  1. 我尝试使用on 方法来监控加载事件,但这不起作用:

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

    我知道这不起作用,因为“加载”事件没有冒泡

  2. 我正在考虑修改我的插件以将“on”放在插件内部,但我不喜欢这个想法,因为它在插件中引入了一些超出范围的行为

  3. 我也可以在每次创建动态节点的时候调用插件,但是也会给其他部分引入依赖。我的插件不会像我希望的那样自主。

有没有人建议处理我的要求,让我的插件尽可能隔离?

[edit]这应该适用于 IE8 及更高版本(理想情况下适用于其他浏览器)

[编辑]这是一个说明问题的jsFiddle(只需单击Add 并尝试单击新创建的元素)。

【问题讨论】:

  • .on() 用于事件处理程序,而不是插件。每次创建动态节点时都必须调用插件,或者使用观察者检查何时将新的特定元素 (.openmode) 添加到 DOM 并根据此编写逻辑。当然,观察者可能会遇到一些性能问题。
  • 在插件内部你没有使用 .on()。
  • @roasted:我不喜欢在插件中使用on()。但是,据我了解我的谷歌搜索,IE 不存在观察者,并且是性能杀手。
  • 不是一个很好的实现恕我直言 - 您不应该捕获 mousedown 事件,而是捕获 click 事件。如果使用键盘导航访问链接,此代码将失败。
  • @Alnitak:你说得对,我会在我的实际代码中更改它

标签: jquery jquery-plugins


【解决方案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 元素启用插件所需的所有工作。

【讨论】:

  • 有趣的方法。我试试看
  • 这是一个很好的解决方案 IMO
  • 对于未来的读者,我更新了我的 jsFiddle 以符合您的建议:jsfiddle.net/stevebeauge/7cqXC/14.
  • 为了保持一致并保持链接,方法的主体是否应该包含return this;
  • 是的,$.fn 方法应该返回 this。 FWIW,这些代码都没有经过测试,无论是语法还是功能 - 它只是在那里显示替代(恕我直言)结构。
【解决方案2】:

我迟到了,但希望它对某人有所帮助。我有同样的问题。

$.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

【讨论】:

  • 我遇到了这个问题。从您的代码中,这是否意味着您在插件的 JS 文件中添加了一个新函数“runPlugin”?我试过了,然后当我调用 runPlugin();正如你所做的那样,我得到了错误 - runPlugin 没有定义。
  • 没错。将函数包装器 Asi 视为一个变量。每次调用它时,都会调用它包含的整个脚本。关于您的问题,我不确定在为插件使用外部 ja 文件时它是否有效。但是,您可以尝试 a) 复制文档中的文件或 b) 在调用插件之前添加 $(document).append(“”)
  • 但是,如果要我选择,我会选择第一个选项,因为它更高效,不需要加载外部文件,使脚本更快更可靠
  • 谢谢。我试试看
  • 所以似乎没有办法在类上调用插件,例如:$(".my_class").somePlugin(),然后,在动态添加类my_plugin的div之后,自动应用该插件到my_plugin 的新实例?调用runPlugin() 与加载动态内容后再次调用$(".my_class").somePlugin() 本质上不一样吗?当我过去这样做时,我总是想知道当我这样做时是否创建了重复变量,或者插件是否“覆盖”了它们现有的实例?
【解决方案3】:

我在插件中找到了一个依赖on() 方法的solution

在这种情况下,插件正在等待 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 插件指南不是很有信心。

所以如果有人有更好的解决方案,我很乐意知道。

【讨论】:

  • 你不应该像这样直接调用$.fn 函数。 $.fn 函数是 jQuery 集合的属性,而不是独立函数(应该在 $ 中)
【解决方案4】:

你仍然可以实现自己的观察者方法,这里扩展 append 方法:

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

应该也可以在旧浏览器中工作,但是,观察者遇到了一些性能问题,即使你可以优化它。

SEE DEMO

【讨论】:

  • 恕我直言,不需要观察者方法。 OP 的插件只做任何有用的来响应用户事件,而 jQuery 已经有一个非常好的机制来处理新添加的 DOM 节点上的事件。
  • @Alnitak 我完全同意
【解决方案5】:

您可以使用所谓的 Mutation Observer 方法对 DOM 中的更改做出反应。以下是有关它的更多信息:

MutationObserver

这里有一些很好的例子:

DOM manipulations

【讨论】:

  • 我最初没有指定它,但我必须针对 IE8 及更高版本。根据您提供的链接,这(尚)不受支持。
猜你喜欢
  • 2012-07-02
  • 2015-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-22
  • 1970-01-01
相关资源
最近更新 更多