Як згадувалось у кількох інших відповідях, події мутації застаріли, тому вам слід використовувати MutationObserver замість цього . Оскільки про це ще ніхто не повідомляв, тут справа йде ...
Основний API JavaScript
API для MutationObserver досить простий. Це не так просто, як події мутації, але все одно це нормально.
function callback(records) {
records.forEach(function (record) {
var list = record.addedNodes;
var i = list.length - 1;
for ( ; i > -1; i-- ) {
if (list[i].nodeName === 'SELECT') {
console.log(list[i]);
}
}
});
}
var observer = new MutationObserver(callback);
var targetNode = document.body;
observer.observe(targetNode, { childList: true, subtree: true });
<script>
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
Давайте розберемо це.
var observer = new MutationObserver(callback);
Це створює спостерігача. Спостерігач ще нічого не спостерігає; саме тут прикріплюється слухач події.
observer.observe(targetNode, { childList: true, subtree: true });
Це змушує спостерігача запуститися. Перший аргумент - це вузол, на якому спостерігач спостерігатиме за змінами. Другий аргумент - варіанти, на що слід слідкувати .
childList
означає, що я хочу стежити за тим, як дочірні елементи додаються або видаляються.
subtree
- це модифікатор, який розширюється childList
для спостереження за змінами в будь-якому місці піддерева цього елемента (в іншому випадку він буде просто розглядати зміни безпосередньо всередині targetNode
).
Два інших основні варіанти, крім цього childList
, attributes
і characterData
, що означають, як вони звучать. Ви повинні використовувати один із цих трьох.
function callback(records) {
records.forEach(function (record) {
Усередині зворотного дзвінка стає трохи хитро. Зворотний дзвінок отримує масив MutationRecord s. Кожен MutationRecord може описувати кілька змін одного типу ( childList
, attributes
або characterData
). Оскільки я лише наказав спостерігачеві стежити childList
, я не буду турбуватися перевіряючи тип.
var list = record.addedNodes;
Тут я захоплюю NodeList усіх дочірніх вузлів, які були додані. Це буде порожнім для всіх записів, де вузли не додаються (а таких записів може бути багато).
З цього моменту я прокручую додані вузли і знаходжу будь-які <select>
елементи.
Тут нічого насправді складного.
jQuery
... але ви просили jQuery. Прекрасно.
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
Це створює нову подію з ім'ям domNodeInserted
, з допомогою JQuery спеціальних подій API . Ви можете використовувати його так:
$(document).on("domNodeInserted", "select", function () {
$(this).combobox();
});
Я особисто пропоную шукати клас, оскільки деякі бібліотеки створюватимуть select
елементи для тестування.
Звичайно, ви також можете використовувати .off("domNodeInserted", ...)
або тонко налаштувати перегляд, передавши такі дані:
$(document.body).on("domNodeInserted", "select.test", {
attributes: true,
subtree: false
}, function () {
$(this).combobox();
});
Це спричинить перевірку зовнішнього вигляду select.test
елемента, коли атрибути змінюються для елементів безпосередньо всередині тіла.
Ви можете побачити його в прямому ефірі нижче або на jsFiddle .
(function($) {
$(document).on("domNodeInserted", "select", function() {
console.log(this);
});
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
<script>
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
</script>
Примітка
Цей код jQuery є досить базовою реалізацією. Це не спрацьовує у випадках, коли модифікації в інших місцях роблять ваш селектор дійсним.
Наприклад, припустимо, що ваш селектор є, .test select
а документ уже має <select>
. Додавання класу test
до <body>
зробить селектор дійсним, але оскільки я лише перевіряю record.target
і record.addedNodes
, подія не запускається. Зміна має відбутися з елементом, який ви хочете вибрати сам.
Цього можна уникнути, запитуючи селектор, коли трапляються мутації. Я вирішив цього не робити, щоб уникнути дублювання подій для елементів, які вже оброблялись. Правильна робота з сусідніми або загальними комбінаторами братів чи сестер зробить справи ще складнішими.
Для більш повного вирішення см https://github.com/pie6k/jquery.initialize , як згадано в Damien Ó Ceallaigh «s відповідь . Однак автор цієї бібліотеки оголосив, що бібліотека стара, і пропонує не використовувати для цього jQuery.
$(select).ready(function() { });