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 сейчас. В конце концов, я выбрал лучшие решения, я также рассмотрел, кто ответил первым, я принял во внимание также, кто вложил много работы в это и т. д. В любом случае, решение, которое я собираюсь реализовать, - это слияние между 2 решениями, решением Аруны и 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, потому что он был первым, кто разместил решение, которое я искал, и он также сделал некоторые изменения, когда я попросил его сделать это. Я также оценил ответ Аруны, потому что он этого заслуживает.

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

11 ответов


вы должны перезагрузить all.js после каждого нового элемента. Это единственное решение, которое я могу придумать.

после действия, которое создает элемент, вы вызовете функцию:

// removed, see Edit

это reevalute all.js и слушатели от all.js прикрепляется к элементам, находящимся в DOM.

это очень неэффективное решение, поэтому, если вы сделаете несколько динамических обновлений, перезагрузите all.js В конце всех обновлений,не после каждого обновление.

это также означает, что некоторые переменные statefull от all.js может быть повторно инициализирован, счетчики могут быть сброшены до нуля и т. д.

я бы лично никогда не сделал такого рода свиньи латания, но если у вас нет другого выбора, попробуйте.

редактировать

проверено работает код

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-код должен быть изменен.

обновление

ИЕ8- совместимая версия (проверено в 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 (), чтобы добавить новый элемент с id 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 as оба поддерживаются вашим клиентом и под защитой.
  • index.html вызывает метод что-то вроде initHeader() который, в свою очередь, вызывает веб-сервис с запросом ajax.
  • а то index.html - это разбор json возвращается вызовом ajax который на самом деле вы возвращаете и можете иметь html и js внутри.
  • этот проанализированный json (html) затем добавляется к странице, как, document.getElementByID('nav').innerHtml = ajaxResponse;

на основе вышеизложенного понимания, единственное место, где вы может изменить / сделать что-то-это контент, который вы вернули из веб-службы.

поэтому я могу предложить вам следующий скрипт, который вы можете добавить в свой ответ webservice в конце, который удалит существующий С 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>

Примечание: я создал новый запрос в хранилище С изменениями


вы можете использовать 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
});

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

в этом случае селектор соответствия элементов во втором аргументе метода "on" не должен существует, когда watcher зарегистрирован.

Вы можете прочитать больше об этом в jQuery по документации

всего доброго!


использовать 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>

обновление: Патч Обезьяны!

// 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>

создайте метод для присоединения события listner в кнопку в все.js

   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>