修改尚未创建的元素的首选方法(除了事件)

时间:2011-02-04 02:20:54

标签: javascript jquery events mutation-events

有很多关于将未来操作绑定到不存在的元素的问题,最终都会以 live / delegate 回答。我想知道如何运行任意回调(例如,添加类或触发插件)匹配选择器的所有现有元素以及与该选择器匹配的所有未来元素< / strong>尚未创建。

似乎livequery插件的主要功能使其成为核心,但另一部分,附加任意回调在某种程度上丢失了。

另一个常见的答案是事件委派,但是如果一个人无法访问创建元素的所有供应商代码以使其trigger事件呢?


以下是一些现实世界的代码:

// with livequery
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .livequery(function(){
        $(this)
            .focus(function(){
                $(this).addClass('active');
            })
            .blur(function(){
                $(this).removeClass('active');
            })
            .addClass('text');
    });

// with live
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
    .live('focus', function(){
            $(this).addClass('active');
        })
    .live('blur', function(){
            $(this).removeClass('active');
        });
    // now how to add the class to future elements?
    // (or apply another plugin or whatever arbitrary non-event thing)

一种方法是监视何时添加/删除新节点并重新触发我们的选择器。感谢 @arnorhs ,我们知道 DOMNodeInserted 事件,我会忽略跨浏览器的问题,希望那些小的IE补丁有一天能够上游到jQuery或知道可以包装jQuery DOM函数。

但是,即使我们可以确保DOMNodeInserted触发了跨浏览器,但使用多个选择器绑定它也是荒谬的。可以随时创建数百个元素,并且可能会对每个元素进行数十个选择器调用。

到目前为止我最好的想法

监视DOMNodeInserted / Deleted和/或挂钩到jQuery的DOM操作例程中只是设置一个“重新初始化”应该发生的标志可能会更好吗?然后可能只有一个定时器,每x秒检查一次该标志,只在DOM实际发生变化时运行所有那些选择器/回调。

如果您以快速的速度添加/删除大量元素(如动画或____),那可能仍然非常糟糕。如果x很低,每x秒重新解析一次DOM,每x秒可能会过于激烈,如果x很高,界面会显得迟钝。

任何其他新颖的解决方案?

我会在它允许的时候添加赏金。我为最新颖的解决方案添加了赏金!

基本上我得到的是一种更强大的,面向方面的方法来操纵DOM。可以允许将来创建新元素,并且应该创建,并将初始document.ready修改应用于它们

JS最近能够做到这么多魔术,我希望它会很明显。

5 个答案:

答案 0 :(得分:10)

在我看来,DOM Level 3事件DOMNodeInsertedhelp(仅针对节点触发)和DOMSubtreeModifiedhelp(几乎任何修改都会触发,如属性更改)是完成该任务的最佳选择

当然,这些事件的最大缺点是,这个世界的互联网探索者不支持他们 (......好吧, IE9 确实如此。)

此问题的另一个合理解决方案是挂钩任何可以修改DOM的方法。但接下来我们要问,这里的范围是什么?

是否足以处理来自特定库(如jQuery)的DOM修改方法?如果由于某种原因另一个库正在修改DOM或甚至是本机方法会怎样?

如果它仅适用于jQuery,我们根本不需要.sub()。我们可以用以下形式编写钩子:

<强> HTML

<div id="test">Test DIV</div>

<强> JS

(function(_append, _appendTo, _after, _insertAfter, _before, _insertBefore) {
    $.fn.append = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: arguments[0]
        });
        return _append.apply(this, arguments);
    };
    $.fn.appendTo = function() {
        this.trigger({
            type: 'DOMChanged',
            newnode: this
        });
        return _appendTo.apply(this, arguments);
    };
    $.fn.after = function() {
        this.trigger({
             type: 'DOMChanged',
             newnode: arguments[0]
         });
        return _after.apply(this, arguments);
    };

    // and so forth

}($.fn.append, $.fn.appendTo, $.fn.after, $.fn.insertAfter, $.fn.before, $.fn.insertBefore));

$('#test').bind('DOMChanged', function(e) {
    console.log('New node: ', e.newnode);
});

$('#test').after('<span>new span</span>');
$('#test').append('<p>new paragraph</p>');
$('<div>new div</div>').appendTo($('#test'));

可以在此处找到上述代码的实例:http://www.jsfiddle.net/RRfTZ/1/

这当然需要完整的DOMmanip方法列表。我不确定您是否可以使用此方法覆盖.appendChild()等本机方法。例如.appendChild位于Element.prototype.appendChild,可能值得一试。

<强>更新

我在Chrome,Safari和Firefox(官方最新版本)中测试了覆盖Element.prototype.appendChild等。适用于Chrome和Safari,但不适用于Firefox!


可能还有其他方法可以解决这一问题。但我想不出一个真正令人满意的方法,比如计算/观察节点的所有后代(需要intervaltimeouts,eeek)。

<强>结论

DOM Level 3事件的混合,支持和挂钩的DOMmanip方法可能是你在这里做的最好的。

答案 1 :(得分:6)

我正在阅读新版本的jQuery版本1.5,我立即想到了这个问题。

使用jQuery 1.5,您实际上可以使用名为jQuery.sub();

的东西创建自己的jQuery版本

这样你就可以在jQuery中覆盖默认的.append(),insert(),. html(),..函数,并创建一个名为“mydomchange”的自定义事件 - 而不会影响所有其他脚本。

所以你可以做这样的事情(从带有小mod的.sub()文档中复制。):

var sub$ = jQuery.sub();
sub$.fn.insert = function() {
    // New functionality: Trigger a domchange event
    this.trigger("domchange");
    // Be sure to call the original jQuery remove method
    return jQuery.fn.insert.apply( this, arguments );
};

您必须对所有dom操作方法执行此操作...

jQuery文档中的jQuery.sub(): http://api.jquery.com/jQuery.sub/

答案 2 :(得分:2)

很棒的问题

似乎有一个可以绑定的自定义事件: http://javascript.gakaa.com/domnodeinserted-description.aspx

所以我猜你可以这样做:

$(document).bind('DOMNodeInserted',function(){ /* do stuff */ });

但我没有尝试过,所以我没有线索......

btw。:相关问题: Can javascript listen for "onDomChange" on every Dom elements?

答案 3 :(得分:0)

没有简单明显的方法可以做到这一点。唯一可靠的方法是主动轮询,这会导致在创建新元素和轮询通知它之间出现渲染打嗝。这也可能会使您的页面占用大量资源,具体取决于您轮询页面的频率。正如您所观察到的,您还可以将这些事件与几个特定于浏览器的事件绑定在一起,至少可以使这些浏览器的效果更好。

您可以覆盖jQuery的DOM修改函数来触发自定义更改事件(并使用$ .live来捕获这些事件以进行操作),但是当我在过去尝试过这个时,它会巧妙地破坏各种jQuery插件(我的猜测)是其中一些插件做类似的事情)。最后,我已经放弃了这样做,因为我不想放弃性能并为主动轮询提供打嗝,而且没有其他综合方法可以做到这一点。相反,我有一个初始化事件,我确保为每个DOM更改触发,我将操作事件绑定到那些。

要小心,如果你不考虑事情,很容易陷入无限的事件循环中,这也可能是微妙的,难以追踪;更糟糕的是,对于您的单元测试不允许的角落情况可能会发生(因此您的用户体验它而不仅仅是您)。在这种意义上,自定义手动触发的初始化事件更容易诊断,因为您始终确切知道何时触发它。

答案 4 :(得分:-1)

嗯,首先,如果你担心穿孔,你甚至不应该使用像这样的选择器。

$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')

任何没有xpath(不仅仅是IE iirc)或getElementsByClassName(IE 7及以下版本)的本机实现的浏览器都可以轻松地在大型网站上花费几秒钟咀嚼它,因此轮询当然会完全消失问题,如果你想要这么广泛。