Предпочтительный способ изменения элементов, которые еще не созданы (помимо событий)

Есть много вопросов о привязывании будущих манипуляций к несуществующим элементам, на которые в конечном итоге отвечает live/делегат. Мне интересно, как выполнить произвольный обратный вызов (например, добавить класс или запустить плагин) до всех существующих элементов, которые соответствуют селектору и всем будущим элементам, которые соответствуют тому же селектору, которые еще не созданы создаваться.

Похоже, что основная функциональность плагина 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 действительно изменился.

Это может быть очень плохо, если вы добавляете/удаляете элементы в больших количествах с высокой скоростью (например, с анимацией или ____). Для повторного анализа DOM один раз для каждого сохраненного селектора каждые x секунд может быть слишком интенсивным, если x является низким, и интерфейс будет казаться вялым, если x высока.

Любые другие новые решения?

Я добавлю щедрость, когда это позволит мне. Я добавил щедрость для самого нового решения!

В основном то, что я получаю, это более аспектно-ориентированный подход для управления DOM. Тот, который может позволить, чтобы новые элементы создавались в будущем, и они должны быть созданы с исходными изменениями document.ready, применяемыми к ним, а также.. p >

В последнее время JS удалось сделать так много волшебства, что я надеюсь, что это будет очевидно.

Ответ 1

По-моему, события DOM уровня 3 DOMNodeInserted help (который срабатывает только для узлов) и DOMSubtreeModified help (который срабатывает практически для любых модификаций, таких как изменения атрибутов) - ваш лучший снимок для выполнения этой задачи.

Конечно, большой недостаток этих событий заключается в том, что интернет-исследователи этого мира не поддерживают их
(... хорошо, IE9).

Другим разумным решением этой проблемы является привязка к любому методу, который может модифицировать DOM. Но тогда мы должны спросить, в чем наш смысл?

Достаточно ли того, чтобы иметь дело с методами модификации DOM из определенной библиотеки, например jQuery? Что делать, если по какой-то причине другая библиотека модифицирует 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, возможно, стоит попробовать.

Обновление

Я тестировал перезапись Element.prototype.appendChild и т.д. в Chrome, Safari и Firefox (официальная последняя версия). Работает в Chrome и Safari, но не в Firefox!


Могут быть другие способы решения этого требования. Но я не могу придумать один подход, который действительно удовлетворяет, например, подсчет/просмотр всех потомков node (для чего потребуется interval или timeouts, eeek).

Заключение

Смесь событий DOM уровня 3, где поддерживаются и подключены методы DOMmanip, вероятно, лучше всего здесь.

Ответ 2

Я читал новую версию jQuery версии 1.5, и я сразу подумал об этом.

С помощью jQuery 1.5 вы можете создать свою собственную версию jQuery с помощью jQuery.sub();

Таким образом, вы можете фактически переопределить функции по умолчанию .append(), insert(),.html(),.. в jQuery и создать собственное собственное событие под названием "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.sub() в документации jQuery: http://api.jquery.com/jQuery.sub/

Ответ 3

Большой вопрос

Кажется, что пользовательское событие можно связать: http://javascript.gakaa.com/domnodeinserted-description.aspx

Итак, я думаю, вы могли бы сделать что-то вроде:

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

Но я не пробовал, поэтому у меня нет подсказки.

btw: родственный вопрос: Может javascript прослушать "onDomChange" на каждом элементе Dom?

Ответ 4

Нет простого очевидного способа сделать это. Единственный верный подход - это активный опрос, который приводит к появлению икоты рендеринга между тем, когда создается новый элемент, и когда опрос замечает его. Это также может сделать вашу страницу занять много ресурсов в зависимости от того, как часто вы просматриваете страницу. Вы также можете связать это, как вы заметили, с привязкой нескольких событий, специфичных для браузера, чтобы, по крайней мере, улучшить работу в этих браузерах.

Вы можете переопределить функции модификации jQuery DOM для запуска настраиваемого события изменения (и использовать $.live для захвата этих событий для манипуляций), но когда я пробовал это в прошлом, он тонко разбивал различные плагины jQuery (моя догадка некоторые из этих плагинов делают что-то подобное). В конце концов я отказался от надежной работы, так как я не хочу отказываться от производительности и показывать икоту активному опросу, и нет другого всеобъемлющего способа сделать это. Вместо этого у меня есть событие инициализации, которое я запускаю для каждого изменения DOM, которое я делаю, и вместо этого привязываю к ним события манипуляции.

Будьте осторожны, легко застрять в бесконечном цикле событий, если вы не продумаете ничего, и это также может быть тонким и трудным для отслеживания; и, что еще хуже, может случиться и в случае с угловым корпусом, которое не выполнялось для вашего тестирования устройства (так что ваши пользователи испытывают это вместо вас). В этом смысле настраиваемое вручную событие инициализации легче диагностировать, так как вы всегда точно знаете, когда вы его запускаете.

Ответ 5

Ну, во-первых, вы даже не должны использовать селектора, если вы беспокоитесь о перфомансе.

$('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 и ниже), может легко потратить несколько секунд, пережевывая это на большом сайте, поэтому опрос, конечно, будет полностью отключен вопроса, если вы хотите, чтобы он был широким.