jQuery插件也可应用于动态创建的元素

11

我正在编写一个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" /> <!-- 没有指定 -->

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

我希望创建一个尽可能通用的插件来实现这种行为。到目前为止,我已经写了:

(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方法监视load事件,但这无效:

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

    我知道这不可行,因为"load"事件不能冒泡。

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

  3. 我也可以在创建动态节点时每次调用插件,但它也会在其他部分引入依赖。我的插件不会像我想象的那样独立。

  4. 有人有建议可以满足我的要求,并尽可能保持我的插件独立吗?

    [编辑] 这应该可以在IE8及更高版本(最好还支持其他浏览器)中使用。

    [编辑] 这里是一个jsFiddle,说明了这个问题(只需点击"Add",然后尝试单击新创建的元素)。


3
.on() 方法是用于事件处理程序,而不是插件。每次创建动态节点时都需要调用插件或使用观察器来检查何时向DOM添加了新的特定元素(.openmode),并根据此编写逻辑。当然,观察器可能会导致一些性能问题。 - A. Wolff
插件内部你没有使用 .on()。 - Ankit Sharma
@roasted:我不喜欢在插件中使用on()。然而,据我所了解,观察者在IE中不存在,并且会影响性能。 - Steve B
1
不好的实现方式,个人认为你不应该捕获 mousedown 事件,而是应该捕获 click 事件。如果使用键盘导航访问链接,此代码将失败。 - Alnitak
@Alnitak:你说得对,我会在我的实际代码中进行更改。 - Steve B
5个回答

8

$.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元素。


有趣的方法。我会尝试一下。 - Steve B
这是我个人认为的一个不错的解决方案。 - A. Wolff
为了保持一致性并保持链式调用,该方法的主体是否应包括return this; - Steve B
我们只能使用这个插件来选择 a.openmode 元素。我们不能用这个插件选择其他类型的元素,比如 a.asdf,即带有 asdf 类的锚点标签。对吗? - Zeel Shah
@zps215 基础插件可应用于任何类 - 最后的代码片段仅是将其应用于具有特定类的所有元素的示例。 - Alnitak
显示剩余2条评论

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未定义。 - N.P
没错。把函数包装器看作一个变量。每次调用它,你都在调用它所包含的整个脚本。关于你的问题,我不确定在使用外部ja文件插件时是否有效。但是,你可以尝试a)将文件复制到你的文档中或b)在调用插件之前添加$(document).append("<script src=[你的插件url]></script>")。 - edvilme
然而,如果我必须做出选择,我会选择第一种选项,因为它更高效,不需要加载外部文件,使脚本更快、更可靠。 - edvilme
谢谢。我会尝试的。 - N.P
看起来没有办法在类上调用插件,例如:$(".my_class").somePlugin(),然后,在添加了一个具有类my_plugin的div之后,自动将该插件应用于my_plugin的新实例?难道调用runPlugin()本质上不是在动态加载内容后再次调用$(".my_class").somePlugin()吗?过去我做过这样的事情,我总是想知道当我这样做时是否会创建重复的变量,或者插件是否会“覆盖”它们现有的实例? - user1063287
在这种情况下,runPlugin 做的唯一一件事情就是启动插件,因为默认情况下它会在 onLoad(添加任何新元素之前)时启动。这样做不会复制插件,因为它只告诉脚本指令的位置在哪里。 - edvilme

1
我发现一个解决方案,它依赖于插件中的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插件指南。
如果有更好的解决方案,我会很高兴知道。

你不应该直接调用 $ .fn 函数。$ .fn 函数是 jQuery 集合的属性,而不是独立的函数(这些函数应该在 $ 中)。 - Alnitak

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

我希望这段代码可以在旧版浏览器中正常运行,但是观察者模式存在一些性能问题,即使你可以进行优化。

查看演示


在我看来,没有必要使用观察者方法。原帖中的插件只有在响应用户事件时才会执行任何“有用”的操作,而jQuery已经有了一个完美处理新添加的DOM节点上事件的机制。 - Alnitak
@Alnitak 我完全同意。 - A. Wolff

0
你可以使用所谓的“Mutation Observer”方法来响应DOM中的更改。这里有更多关于它的信息: MutationObserver 以下是一些很好的示例: DOM manipulations

我最初没有指定,但我必须针对IE8及更高版本进行目标设置。根据您提供的链接,这还不被支持。 - Steve B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接