Предпочтительный способ изменения элементов, которые еще не созданы (кроме событий) - PullRequest
42 голосов
/ 04 февраля 2011

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

Кажется, что основная функциональность плагина 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 и / или подключиться к подпрограммам манипулирования DOM в jQuery, чтобы установить только флаг того, что должно произойти «re-init»? Тогда может существовать таймер, который проверяет этот флаг каждые x секунд, и запускает все эти селекторы / обратные вызовы, когда DOM действительно изменился.

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

Какие-нибудь другие новые решения?

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

По сути, я имею в виду более аспектно-ориентированный подход к управлению DOM. Тот, который может позволить, что новые элементы будут созданы в будущем , и они должны быть созданы с исходным документом. Для них также применены модификации .

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

Ответы [ 5 ]

10 голосов
/ 12 февраля 2011

По моему мнению, события DOM Level 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!


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

Заключение

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

6 голосов
/ 09 февраля 2011

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

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

Таким образом, вы можете переопределить стандартные функции .append (), insert (), .html (), .. в jQuery и создать свое собственное событие, называемое что-то вроде «mydomchange»), не затрагивая все остальные сценарии.

Таким образом, вы можете сделать что-то подобное (скопировано из документации .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 );
};

Вы должны сделать это со всеми методами манипуляций с домом ...

jQuery.sub () в документации по jQuery: http://api.jquery.com/jQuery.sub/

2 голосов
/ 04 февраля 2011

Отличный вопрос

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

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

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

Я не пробовал, так что понятия не имею ..

btw .: связанный вопрос: Может ли javascript прослушивать "onDomChange" на всех элементах Dom?

0 голосов
/ 12 февраля 2011

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

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

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

0 голосов
/ 12 февраля 2011

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

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

...