问题 jQuery插件也适用于动态创建的元素


我正在编写一个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. 我也可以在每次创建动态节点时调用插件,但它也会将依赖项引入其他部分。我的插件不会像我想的那样自主。

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

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

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


7095
2018-05-14 12:00


起源

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


答案:


插件添加到 $.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 元件。


8
2018-05-14 13:03



有趣的方法。我试一试 - Steve B
这是一个很好的解决方案IMO - A. Wolff
Maye代码中的拼写错误(两个 case 'on': 条款) - Steve B
对于未来的读者,我更新我的jsFiddle以符合您的建议: jsfiddle.net/stevebeauge/7cqXC/14。 - Steve B
为了保持一致,并保持链接,该方法的主体应该包括 return this; ? - Steve B


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

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

2
2017-07-06 22:12



我遇到过这个问题。从您的代码中,是否意味着您在插件的JS文件中添加了一个新函数“runPlugin”?我试了一下然后调用runPlugin();正如你所做的那样,我得到了错误 - 没有定义runPlugin。 - N.P
那就对了。把函数包装器Asi想象成一个变量。每次调用它时,都会调用它包含的整个脚本。关于你的问题,我不确定它是否适用于插件的外部ja文件。但是,你可以尝试a)复制文件中的文件或b)在调用插件之前添加$(document).append(“<script src = [你的插件的url]> </ script>”) - edvilme
但是,如果我选择,我会选择第一个选项,因为它更有效,不需要加载外部文件,使脚本更快,更可靠 - edvilme
谢谢。我会试试看 - N.P
所以似乎没有办法在类上调用插件,例如: $(".my_class").somePlugin(),然后,在与班级的div之后 my_plugin 动态添加,将该插件自动应用于该新实例 my_plugin?不打电话 runPlugin() 与调用基本相同 $(".my_class").somePlugin() 加载动态内容后再次?当我在过去这样做时,我总是想知道在我这样做时是否创建了重复的变量,或者插件是否“覆盖”了他们现有的实例? - user1063287


我发现了一个  依赖于 on() 方法,在插件中。

在这种情况下插件等待a 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插件指南并不十分自信。

因此,如果有人有更好的解决方案,我会很高兴知道。


1
2018-05-14 12:38



你永远不应该打电话给 $.fn 功能直接这样。 $.fn 函数是jQuery集合的属性,而不是独立函数(应该是 $) - Alnitak


你仍然可以实现自己的观察者方法,这里扩展了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);

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

看看演示


1
2018-05-14 13:11



恕我直言,不需要观察员方法。 OP的插件只做任何事情 有用 为了响应用户事件,jQuery已经有了一个非常好的机制来处理新添加的DOM节点上的事件。 - Alnitak
@Alnitak我完全同意 - A. Wolff


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

MutationObserver

以下是一些很好的例子:

DOM操作


0
2018-05-14 12:05



我最初没有指定它,但我必须针对IE8及更高版本。根据您提供的链接,这尚未得到支持。 - Steve B