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>