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

发布于 2024-10-15 17:27:09 字数 2174 浏览 6 评论 0 原文

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

看来 livequery 插件的主要功能已成为核心,但附加任意回调的另一部分却不知何故丢失了。

另一个常见的答案是事件委托,但是如果无法访问正在创建元素的所有供应商代码怎么办触发事件?


这是一些现实世界的代码:

// 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 较高,则界面会显得缓慢。

还有其他新颖的解决方案吗?

如果允许的话,我会添加赏金。我已经为最新颖的解决方案添加了赏金!

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

JS 最近已经能够创造如此多的魔力,我希望它会变得显而易见。

There are a lot of questions about binding future manipulations to non-existent elements that all end up answered with live/delegate. I am wondering how to run an arbitrary callback (to add a class or trigger a plugin, for example) to all existing elements that match a selector and all future elements that match that same selector that are yet to be created.

It seems that the main functionality of the livequery plugin made it into the core but the other part, attaching arbitrary callbacks got lost along the way somehow.

Another common answer is event delegation but what if one doesn't have access to all of the vendor code that is creating the elements to have it trigger the events?


Here is some real-world code:

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

One approach would be to monitor when new nodes are added/removed and re-trigger our selectors. Thanks to @arnorhs we know about the DOMNodeInserted event, which I would ignore the cross-browser problems in the hope that those small IE patches could someday land upstream to jQuery or knowing the jQuery DOM functions could be wrapped.

Even if we could ensure that the DOMNodeInserted fired cross-browser, however, it would be ridiculous to bind to it with multiple selectors. Hundreds of elements can be created at any time, and making potentially dozens of selector calls on each of those elements would crawl.

My best idea so far

Would it maybe be better to monitor DOMNodeInserted/Deleted and/or hook into jQuery's DOM manipulation routines to only set a flag that a "re-init" should happen? Then there could just be a timer that checks that flag every x seconds, only running all those selectors/callbacks when the DOM has actually changed.

That could still be really bad if you were adding/removing elements in great numbers at a fast rate (like with animation or ____). Having to re-parse the DOM once for each saved selector every x seconds could be too intense if x is low, and the interface would appear sluggish if x is high.

Any other novel solutions?

I will add a bounty when it lets me. I have added a bounty for the most novel solution!

Basically what I am getting at is a more aspect-oriented approach to manipulating the DOM. One that can allow that new elements are going to be created in the future, and they should be created with the initial document.ready modifications applied to them as well.

JS has been able to do so much magic lately that I'm hoping it will be obvious.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

嘴硬脾气大 2024-10-22 17:27:09

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

当然,这些事件的最大缺点是,这个世界的 Internet Explorer 不支持它们
(...嗯,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 中,可能值得一试。

update

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


可能还有其他方法来满足该要求。但我想不出一种真正令人满意的方法,比如计算/观察节点的所有后代(这需要一个间隔或超时,哎呀)。

结论

DOM 级别 3 事件的混合,其中支持的和挂钩的 DOMmanip 方法可能是您在这里可以做的最好的事情。

In my opinion, the DOM Level 3 events DOMNodeInsertedhelp (which fires only for nodes) and DOMSubtreeModifiedhelp (which fires for virtually any modification, like attribute changes) are your best shot to accomplish that task.

Of course, the big downside of those events is, that the Internet Explorers of this world don't support them
(...well, IE9 does).

The other reasonable solution for this problem, is to hook into any method Which can modify the DOM. But then we have to ask, what is our scope here?

Is it just enough to deal with DOM modification methods from a specific library like jQuery? What if for some reason another library is modifying the DOM or even a native method ?

If it's just for jQuery, we don't need .sub() at all. We could write hooks in the form of:

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

A live example of the above code can be found here: http://www.jsfiddle.net/RRfTZ/1/

This of course requires a complete list of DOMmanip methods. I'm not sure if you can overwrite native methods like .appendChild() with this approach. .appendChild is located in Element.prototype.appendChild for instance, might be worth a try.

update

I tested overwriting Element.prototype.appendChild etc. in Chrome, Safari and Firefox (official latest release). Works in Chrome and Safari but not in Firefox!


There might be other ways to tackle the requirement. But I can't think of a single approach which is really satisfying, like counting / watching all descendents of a node (which would need an interval or timeouts, eeek).

Conclusion

A mixture of DOM Level 3 events where supported and hooked DOMmanip methods is probably the best you can do here.

幽蝶幻影 2024-10-22 17:27:09

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

在 jQuery 1.5 中,您实际上可以使用 jQuery.sub(); 来创建您自己的 jQuery 版本。

这样,您实际上可以覆盖 jQuery 中默认的 .append()、insert()、.html() 等函数,并创建您自己的自定义事件(称为“mydomchange”),而不会影响所有其他脚本。

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

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/

I was reading up on the new release of jQuery, version 1.5 and I immediately thought of this question.

With jQuery 1.5 you can actually create your own version of jQuery by using something called jQuery.sub();

That way you can actually override the default .append(), insert(), .html(), .. functions in jQuery and create your own custom event called something like "mydomchange" - without it affecting all other scripts.

So you can do something like this (copied from the .sub() documentation with minor mod.):

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

You would have to do this to all the dom manipulation methods...

jQuery.sub() in the jQuery documention:
http://api.jquery.com/jQuery.sub/

七分※倦醒 2024-10-22 17:27:09

好问题

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

所以我想你可以做类似的事情:

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

但我没有尝试过,所以我不知道..

顺便说一句:相关问题:
javascript 可以监听每个 Dom 元素上的“onDomChange”吗?

Great question

There seems to be a custom event you can bind:
http://javascript.gakaa.com/domnodeinserted-description.aspx

So I guess you could do something like:

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

But I haven't tried so I don't have a clue..

btw.: related question:
Can javascript listen for "onDomChange" on every Dom elements?

旧时光的容颜 2024-10-22 17:27:09

没有简单明显的方法可以做到这一点。唯一可靠的方法是主动轮询,这会导致在创建新元素和轮询注意到它之间出现渲染中断。这还可能使您的页面占用大量资源,具体取决于您轮询页面的频率。正如您所观察到的,您还可以将其与绑定多个特定于浏览器的事件结合起来,至少使这些浏览器中的事情运行得更好。

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

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

There is no simple obvious way to do it. The only surefire approach is active polling, which causes there to be a render hiccup between when the new element is created and when the polling notices it. That can also make your page take a lot of resources depending on how frequently you poll the page. You can also couple this, as you observed, with binding several browser-specific events to at least make things work out better in those browsers.

You can override jQuery's DOM modification functions to trigger a custom change event (and use $.live to catch those events for manipulation), but when I've tried this in the past, it's subtly broken various jQuery plugins (my guess is some of those plugins do something similar). In the end I've given up on doing so reliably since I don't want to give up the performance and render hiccups to active polling, and there is no other comprehensive way to do it. Instead I have an initialization event I make sure to trigger for each DOM change I make, and I bind my manipulation events to those instead.

Be careful, it's easy to get stuck in an infinite event loop if you don't think things through, and this can also be subtle and difficult to track down; and worse yet may happen for a corner case your unit testing didn't allow for (so your users experience it instead of just you). The custom manually triggered initialization event is easier to diagnose in this sense since you always know exactly when you're triggering it.

空心↖ 2024-10-22 17:27:09

好吧,首先,如果您担心性能,您甚至不应该使用这样的选择器。

$('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 及更低版本)的浏览器都可以轻松地在大型网站上花费几秒钟的时间来研究它,因此轮询当然是完全不可能的如果你想要那么广泛的话。

Well, first of all, you shouldn't even be using selectors like this if you're worried about perf.

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

Any browser that doesn't have native implementations of xpath (more than just IE iirc) or getElementsByClassName (IE 7 and below) could easily spend a few seconds chewing on that on a big site so polling would of course be completely out of the question if you want it that broad.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文