JavaScript прикрепляет события к элементам, динамически отображаемым
У меня есть 2 файла index.html
и all.js
.
all.js
(function(){
if (document.getElementById('btn1')){
document.getElementById("btn1").addEventListener("click", displayMessage);
}
function displayMessage(){
alert("Hi!");
}
})()
index.html
Пример 1: - рабочий пример
<button id="btn1">Hit me !</button>
<script type="text/javascript" src="all.js"></script>
Пример 2: - нерабочий пример
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
}
</script>
<script type="text/javascript" src="all.js"></script>
Описание:
В первом примере btn1
был отображен сразу, и событие было привязано к нему. Во втором примере btn1
отображается только при нажатии на btn2
, и после этого, если вы нажмете btn1
, ничего не произойдет.
Как я могу прикрепить событие к btn1
без изменения all.js
?
Вот полный код репозиторий
Примечание. Не спрашивайте меня, почему я пытаюсь сделать что-то вроде этого, не спрашивайте меня, почему мне не разрешено изменять all.js
, потому что этот пример является упрощенным один, в моем случае all.js
файл содержит тысячи строк с большим количеством событий на многих элементах. Решение JavaScript означает, что мне не разрешено использовать другие внешние библиотеки.
ОБНОВЛЕНИЕ: принятие ответа
У меня появилось много разных ответов. Некоторые из вас усердно работали над решением этой проблемы, некоторые из вас работали меньше, но хорошо, что я решил. У меня есть некоторые рабочие решения, но некоторые из них работали только в этом конкретном случае, не принимая во внимание, что мой настоящий файл all.js имеет 4029 строк кода, некоторые решения предлагают использовать делегирование событий, которые я согласен, но, к сожалению, я не могу изменить все мои .js. В конце концов я выбрал лучшие решения, я также рассмотрел, кто ответил первым, я также принял во внимание, кто много работал над этим и т.д. В любом случае, решение, которое я собираюсь реализовать, - это слияние между 2 решения, решение Aruna и Vlacav (+ некоторые изменения самим собой), и вот он:
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
resetJs('all.js');
}
function resetJs(path) {
var scripts = document.getElementsByTagName('script')
, newScript = document.createElement("script");
newScript.src = path + "?timestamp=" + Math.round(new Date().getTime()/1000);
for (var i = 0; i < scripts.length; ++i) {
var srcUrl = scripts[i].getAttribute('src');
if (srcUrl && srcUrl.indexOf(path) > -1) {
scripts[i].parentNode.replaceChild(newScript, scripts[i]);
}
}
}
К сожалению, я не могу разделить репутацию, и я должен отдать ее только одному из них, и я хочу передать это Vlacav, потому что он был первым, кто опубликовал решение, которое я искал, и он также сделал некоторые изменения на нем, когда я попросил его сделать это. Я также оценил ответ Аруны, потому что он этого заслуживает.
Я разместил решение на отдельной ветке здесь, и я также создал несколько других ветвей на этом репо с другими решениями, которые я потому что они могут быть полезны в других ситуациях.
Ответы
Ответ 1
Вам необходимо перезагрузить all.js
после каждого нового элемента. Это единственное решение, о котором я могу думать.
После действия, которое создает элемент, вы вызовете функцию:
// removed, see Edit
это должно повторить all.js
, а слушатели из all.js
должны присоединяться к элементам, находящимся в DOM.
Это очень неэффективное решение, поэтому, если вы делаете несколько динамических обновлений, перезагрузите all.js
в конце всех обновлений, а не после каждого обновления.
Это также означает, что некоторые statefull переменные из all.js
могут быть повторно инициализированы, счетчики могут быть reset равными нулю и т.д.
Я лично никогда не сделаю такого рода копирование, но если у вас нет другого выбора, попробуйте.
Изменить
Протестированный рабочий код
function reloadAllJs(){
var path = "all.js"
, oldScript = document.querySelector("script[src^='" + path + "']")
, newScript = document.createElement("script")
;
// adding random get parameter to prevent caching
newScript.src = path + "?timestamp=" + Date.now();
oldScript.parentNode.replaceChild(newScript, oldScript);
}
querySelector
и Date.now()
совместимы с IE9 +, для поддержки IE8 - код должен быть изменен.
Update
IE8-совместимая версия (протестирована в IE7)
function reloadAllJs(){
var path = "all.js"
, scripts = document.getElementsByTagName("script")
, oldScript
, newScript = document.createElement("script")
;
for(var i = 0, s; s = scripts[i]; i++) {
if (s.src.indexOf(path) !== -1) {
oldScript = s;
break;
}
}
// adding random get parameter to prevent caching
newScript.src = path + "?timestamp=" + (new Date()).getTime();
oldScript.parentNode.replaceChild(newScript, oldScript);
}
Примечание. IE8 не поддерживает addEventListener
(он имеет очень похожий метод attachEvent
), поэтому, если ваш all.js
полагается на addEventListener
, то all.js
совместим только с IE9 +.
Ответ 2
Добавьте атрибут onclick в элемент btn.
function displayMessage() {
alert("Hi!");
}
function show() {
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
btn.onclick = displayMessage;
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
}
<button id="btn2" onclick="show()">Show</button>
Ответ 3
ПРОБЛЕМА С КОДОМ:
Проблема заключается в том, что вы используете addEventListener
в блоке, который выполняется при загрузке файла all.js
. Таким образом, он добавит EventListener
для всех ID ( btn1 )
, которые присутствуют на странице до того, как all.js
будет вызван. Когда вы вызываете show() для добавления нового элемента с id btn1
, all.js не может добавить EventListner
для этого элемента.
Итак, если вы добавляете новый элемент, вам снова нужно использовать addEventListener.
РЕШЕНИЕ 1:
используйте addEventListner после того, как элемент вставлен в тело
function show() {
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
// btn.onclick = displayMessage; // you can use this also.
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
//document.getElementById("btn1").addEventListener("click", displayMessage); //like this
}
Я не думаю, что вы ищете это решение.
РЕШЕНИЕ 2:
Если вы также используете JQuery Library, то это будет легко, но вы также используете чистый javascript.
Вам просто нужно перезагрузить all.js после вставки элементов.
Ниже представлен ваш index2.html с идеальным решением с jquery. если у вас нет Jquery, то перезагрузите all.js с помощью javascript.
<html>
<body>
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript" src="../jquery.js"></script>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
//HERE YOU HAVE TO RELOAD YOUR all.js
reload_js('all.js');
}
</script>
<script type="text/javascript">
function reload_js(src) {
$('script[src="' + src + '"]').remove();
$('<script>').attr('src', src).appendTo('head');
}
</script>
<script type="text/javascript" src="all.js"></script>
</body>
</html>
РЕДАКТИРОВАТЬ Только Javascript
<html>
<body>
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
//Pass the name you want to reload
loadJs('all.js');
}
</script>
<script type="text/javascript" src="all.js"></script>
<script type="text/javascript">
function loadJs(js)
{
//selecting js file
var jsSection = document.querySelector('script[src="'+js+'"]');
//selecting parent node to remove js and add new js
var parent = jsSection.parentElement;
//deleting js file
parent.removeChild(jsSection);
//creating new js file
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= js;
//adding new file in selected tag.
parent.appendChild(script);
}
</script>
</body>
</html>
Дополнительные параметры для перезагрузки. Посмотрите, как перезагрузить javascript файлы здесь
Вот обновление репозиторий
Ответ 4
Вы также можете использовать делегирование событий.
Если новые элементы DOM размещены в div с идентификатором "dynamic", вы можете использовать следующее:
document.getElementById("dynamic").addEventListener("click",function(event){
var target = event.target;
console.log(target);
});
Это приведет к захвату всех событий кликов, которые происходят в динамическом, и вы можете проверить цель события, чтобы определить, какой элемент был нажат. Для этого вам нужен только один обработчик событий.
Ответ 5
Во время загрузки all.js
он ищет элемент btn1
и пытается присоединить событие, если оно представлено. Поскольку элемент создается/загружается динамически, он не привязывается к событию, когда загружается all.js
, и поэтому элемент не привязан к этому событию после его создания.
В соответствии с обсуждением, которое я имел с вами, я понял следующие вещи от вас:
- Вы не можете изменить
all.js
и index.html
, поскольку оба поддерживаются
вашим клиентом и защищенным.
-
index.html
вызывает метод примерно как initHeader()
который, в свою очередь, вызывает веб-сервис с помощью ajax-запроса.
- И затем
index.html
анализирует json
, возвращенный вызовом ajax
который на самом деле вы возвращаетесь и может иметь html и js внутри.
- Этот разобранный json (html) затем добавляется к странице, например,
document.getElementByID('nav').innerHtml = ajaxResponse;
Основываясь на приведенных выше пониманиях, единственным местом, которое вы можете изменить/сделать что-то, является содержимое, которое вы вернули из веб-службы.
Итак, я могу предложить вам ниже script, который вы можете добавить в свой ответ веб-службы в конце, который удалит существующий all.js
файл из index.html
и снова загрузит его после ответа ajax.
<script type="text/javascript">
function reattachEvents() {
// Remove 'all.js'
var scripts = document.getElementsByTagName('script')
for (var i = scripts.length - 1; i >= 0; i--){ // Loop backwards
if (scripts[i]) {
var srcUrl = scripts[i].getAttribute('src');
if(srcUrl && srcUrl.indexOf('all.js') > -1) {
scripts[i].parentNode.removeChild(scripts[i]);
}
}
}
// Load 'all.js'
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'all.js';
head.appendChild(script);
}
reattachEvents();
</script>
Примечание. Я создал новый запрос на перенос в репозиторий с этими изменениями
Ответ 6
Вы можете использовать jQuery для загрузки script при каждом нажатии кнопки
<button id="btn2" onclick="show()">Show</button>
<script
src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
jQuery.getScript("all.js");
}
</script>
Ответ 7
Я думаю, вы должны создать элемент при начальной загрузке либо в JS, либо в HTML
Пожалуйста, проверьте эти примеры.
Пример 1:
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript">
function initHiddenButton(id, text) {
var btn = document.createElement("BUTTON");
btn.setAttribute("id", id);
btn.innerHTML = text;
btn.style.display = 'none';
document.body.appendChild(btn);
}
initHiddenButton("btn1", "Hit me !");
function show(){
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
btn1.style.display = 'inline-block';
document.body.insertBefore(btn1, btn2);
//others code...
}
</script>
<script src="all.js"></script>
Пример 2:
<button id="btn2" onclick="show()">Show</button>
<!-- hidden buttons -->
<button id="btn1" onclick="show()" style="display: none;">Hit me !</button>
<script type="text/javascript">
function show(){
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
btn1.style.display = 'inline-block';
document.body.insertBefore(btn1, btn2);
//others code...
}
</script>
<script src="all.js"></script>
Ответ 8
Использование чистых js Вы можете сделать это следующим образом:
document.querySelector('body').addEventListener('click', function (event) {
if (event.target.id !== 'btn1') {
return; //other element than #btn1 has been clicked
}
//Do some stuff after clicking #btn1
});
Если вы можете использовать jQuery, у него есть метод "on", который можно вызвать как ниже
$('body').on('click', '#btn1', function(event) {
// do something when #btn has been clicked
});
Вы привязываетесь к клиенту-слушателю к телу (или к любому другому элементу, который содержит # btn1), и если он был запрограммирован, то второй аргумент фильтрует, если он был запрограммирован С# btn1, если он не активирует обратный вызов.
В этом случае селектор соответствия элементов во втором аргументе метода "on" не должен существовать при регистрации наблюдателя.
Подробнее об этом читайте в jQuery в документации
Счастливый взлом!
Ответ 9
Используйте MutationObserver для прослушивания изменений dom и добавления событий в addedNodes
function displayMessage(){
alert('Hi!');
}
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
}
(new MutationObserver(mutations => {
mutations
.reduce((prev, cur) => prev.concat(Array.from(cur.addedNodes)), [])
.filter(node => node.id === "btn1")
.forEach(button => button.addEventListener('click', displayMessage));
})).observe(document.body, {childList: true, subtree: true})
<button id="btn2" onclick="show()">Show</button>
Ответ 10
В вашем случае обработчик displayMessage()
btn1
будет пытаться зарегистрироваться первым до того, как btn1
будет зарегистрирован в DOM, когда пользователь нажмет на btn2
элемент btn1
будет зарегистрирован в DOM. Таким образом, не будет никакого обработчика для запуска. Итак, зарегистрируйте обработчик после регистрации btn1
, как показано ниже.
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
btn.addEventListener("click", displayMessage);
}
</script>
<script type="text/javascript" src="all.js"></script>
Ответ 11
создайте метод для добавления списка событий в кнопку в файле all.js
function attachClickEvent(elementId, event){
var element = document.getElementById(elementId);
if (element ){
element.addEventListener("click", event);
}
}
function displayMessage(){
alert("Hi!");
}
index.html
<button id="btn2" onclick="show()">Show</button>
<script type="text/javascript">
function show(){
var btn = document.createElement("BUTTON");
btn.setAttribute("id", "btn1");
btn.innerHTML = "Hit me !";
document.getElementById("btn2");
document.body.insertBefore(btn, btn2);
//now call attachClickEvent function to add event listner to btn1
//(both displayMessage() and attachClickEvent() functions are loaded now)
attachClickEvent('btn1', displayMessage);
}
</script>
<script type="text/javascript" src="all.js"></script>