JavaScript присоединяет события к элементам, отображаемым динамически
У меня есть 2 файла index.html и all.js.
Все.js
(function(){
if (document.getElementById('btn1')){
document.getElementById("btn1").addEventListener("click", displayMessage);
}
function displayMessage(){
alert("Hi!");
}
})()
Индекс.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 решение означает, что мне не разрешается использовать другие внешние библиотеки.
Обновление: принятие ответа
Я получил много разных ответов. Некоторые из вас работали больше, чтобы решить эту проблему, некоторые из вас работали меньше, но хорошо, что я решил. Я получил несколько рабочих решений, но некоторые из них работали только для этого конкретного случая, не принимая во внимание, что мое настоящее все.файл js содержит 4029 строк кода, некоторые решения предлагают использовать делегирование событий, с которыми я согласен, но, к сожалению, я не могу изменить все.JS теперь файл. В конце концов, я выбрал лучшие решения, я также рассмотрел, кто ответил первым, я принял во внимание также, кто вложил много работы в это и т. д. В любом случае, решение, которое я собираюсь реализовать, - это слияние между двумя решениями, решением Аруны и влак-ва (+ некоторые изменения моей собственной личности), и вот оно:
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]);
}
}
}
К сожалению, я не могу разделить репутацию, и я должен отдать ее только одному из них, и я хочу отдать это влак, потому что он был первым, кто разместил сообщение решение, которое я искал, и он также сделал некоторые изменения на нем, когда я попросил его сделать это. Я также оценил ответ Аруны, потому что он этого заслуживает.
Я разместил решение в отдельной ветке здесь, а также создал несколько других ветвей на этом РЕПО с другими решениями, которые я получил, потому что они могут быть полезны в других ситуациях.
11 ответов:
Вы должны перезагрузить
all.jsпосле каждого нового элемента. Это единственное решение, которое я могу придумать.После действия, которое создает элемент, вы вызовете функцию:
// removed, see EditЭто повторит
Это очень неэффективное решение, поэтому, если вы делаете несколько динамических обновлений, перезагрузитеall.jsи слушатели изall.jsприсоединятся к элементам, находящимся в DOM.all.jsв конце всех обновлений, не после каждого обновления.Это также означает, что некоторые переменные statefull из
Я бы лично никогда не стал делать такого рода заплатки для свиней, но если у вас нет другого выбора, попробуйте.all.jsмогут быть повторно инициализированы, счетчики могут быть сброшены до нуля и т. д.Edit
Протестированный рабочий код
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-код должен быть изменен.Обновление
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+.
Добавьте атрибут 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>
ПРОБЛЕМА С КОДОМ:
Проблема заключается в том, что вы используете
addEventListenerв блоке, который выполняется при загрузке файлаall.js. Таким образом, он добавитEventListenerдля всех идентификаторов( btn1 ), которые присутствуют на странице перед вызовомall.js. Когда вы вызываете show (), чтобы добавить новый элемент с идентификаторомbtn1, все.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, то это будет легко, но вы также используете чистый javascript.
Вы просто должны перезагрузить все ваши.js после вставки элементов.
Ниже приведен ваш index2.html с идеальным решением с помощью jquery. если у вас нет Jquery, то перезагрузите свой все.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 файлы здесь
Вот обновление репозитория
Вы также должны иметь возможность использовать делегирование событий.
Если все новые элементы DOM помещены в div с идентификатором "dynamic", вы можете использовать следующее:
document.getElementById("dynamic").addEventListener("click",function(event){ var target = event.target; console.log(target); });Это захватит все события щелчка, которые происходят в динамическом div, и вы можете проверить цель события, чтобы определить, какой элемент был нажат. При таком подходе требуется только один обработчик событий.
В момент загрузки
Из беседы, которую я имел с вами, я понял от вас следующее:]}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;Исходя из вышеизложенного понимания, единственное место, где вы можете что-то изменить / сделать, - это контент, который вы вернули из веб-сервис.
Поэтому я могу предложить вам следующий скрипт, который вы можете добавить в свой ответ webservice в конце, который удалит существующий файл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>Примечание : я создал новый запрос pull в вашем репозитории с этими изменениями
Вы можете использовать jQuery для загрузки скрипта при каждом нажатии кнопки
<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>
Я думаю, что вы должны создать элемент при начальной загрузке либо в 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>
Используя pure 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 });Вы привязываете прослушиватель click к телу (или любому другому элементу, содержащему #btn1), и если он был триггирован, то второй аргумент фильтрует, если он был триггирован из #btn1, если нет, то он не вызовет обратный вызов.
В этом случае селектор соответствия элементов во втором аргументе метода" on " не должен существовать, когда наблюдатель является зарегистрированный.
Подробнее об этом можно прочитать в jQuery on documentation
Счастливого взлома!
Используйте 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>UPDATE : Monkey Patch!
// your hack // listen for all nodes (new MutationObserver(mutations => { mutations .reduce((prev, cur) => prev.concat(Array.from(cur.addedNodes)), []) .forEach(replicateEvents); })).observe(document.body, {childList: true, subtree: true}) // override addEventListener const initialEvents = {}; const oldAddListener = Element.prototype.addEventListener; Element.prototype.addEventListener = function(type, cb) { if(this.id) { var events = initialEvents[this.id] || (initialEvents[this.id] = {types: {}}); if(!events.types[type]){ events.target = this; events.types[type] = [cb]; } else if(events.target === this) { events.types[type].push(cb); } } oldAddListener.apply(this, Array.from(arguments)); }; function replicateEvents(node){ let events = initialEvents[node.id]; if(!events) return; Object.keys(events.types) .forEach(type => events.types[type].forEach(cb => oldAddListener.call(node, type, cb))); } function show(){ var btn = document.createElement("BUTTON"); btn.setAttribute("id", "btn1"); btn.innerHTML = "Hit me !"; document.getElementById("btn2"); document.body.insertBefore(btn, btn2); } // all.js (function(){ if (document.getElementById('btn1')){ document.getElementById("btn1").addEventListener("click", displayMessage); document.getElementById("btn1").addEventListener("click", displayMessage2); } function displayMessage(){ alert("Hi!"); } function displayMessage2(){ console.log("Hi2!"); } })()<button id="btn2" onclick="show()">Show</button> <br /> <button id="btn1">Some initial button</button>
В вашем случае
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>
Создайте метод для прикрепления списка событий к кнопке в all.js file
function attachClickEvent(elementId, event){ var element = document.getElementById(elementId); if (element ){ element.addEventListener("click", event); } } function displayMessage(){ alert("Hi!"); }Индекс.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>
Comments