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]);
}
}
}


К сожалению, я не могу разделить репутацию, и я должен отдать ее только одному из них, и я хочу отдать это влак, потому что он был первым, кто разместил сообщение решение, которое я искал, и он также сделал некоторые изменения на нем, когда я попросил его сделать это. Я также оценил ответ Аруны, потому что он этого заслуживает.



Я разместил решение в отдельной ветке здесь, а также создал несколько других ветвей на этом РЕПО с другими решениями, которые я получил, потому что они могут быть полезны в других ситуациях.
801   11  

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

    Ничего не найдено.