Czy można dołączyć do innerHTML bez niszczenia detektorów zdarzeń potomków?

112

W poniższym przykładowym kodzie dołączam onclickprocedurę obsługi zdarzeń do zakresu zawierającego tekst „foo”. Program obsługi to anonimowa funkcja, która wyświetla plik alert().

Jeśli jednak przypiszę do węzła nadrzędnego innerHTML, ten onclickprogram obsługi zdarzeń zostanie zniszczony - kliknięcie „foo” nie powoduje wyświetlenia okna alertu.

Czy można to naprawić?

<html>
 <head>
 <script type="text/javascript">

  function start () {
    myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    mydiv = document.getElementById("mydiv");
    mydiv.innerHTML += "bar";
  }

 </script>
 </head>

 <body onload="start()">
   <div id="mydiv" style="border: solid red 2px">
     <span id="myspan">foo</span>
   </div>
 </body>

</html>
mikrofon
źródło
To pytanie jest związane z problemem „Dodawanie nowych pól do formularza usuwa dane wprowadzone przez użytkownika”. Wybrana odpowiedź bardzo dobrze rozwiązuje oba te problemy.
MattT,
Do rozwiązania tego problemu można użyć delegowania wydarzeń.
dasfdsa

Odpowiedzi:

127

Niestety przypisanie do innerHTMLpowoduje zniszczenie wszystkich elementów podrzędnych, nawet jeśli próbujesz dołączyć. Jeśli chcesz zachować węzły potomne (i ich programy obsługi zdarzeń), musisz użyć funkcji DOM :

function start() {
    var myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    var mydiv = document.getElementById("mydiv");
    mydiv.appendChild(document.createTextNode("bar"));
}

Edycja: rozwiązanie Boba, z komentarzy. Opublikuj swoją odpowiedź, Bob! Zdobądź uznanie. :-)

function start() {
    var myspan = document.getElementById("myspan");
    myspan.onclick = function() { alert ("hi"); };

    var mydiv = document.getElementById("mydiv");
    var newcontent = document.createElement('div');
    newcontent.innerHTML = "bar";

    while (newcontent.firstChild) {
        mydiv.appendChild(newcontent.firstChild);
    }
}
Ben Blank
źródło
Czy istnieje zamiennik, który może dołączyć dowolny fragment kodu HTML?
mike
64
newcontent = document.createElement ('div'); newcontent.innerHTML = arbitrary_blob; while (newcontent.firstChild) mydiv.appendChild (newcontent.firstChild);
bobince
Świetnie, Bob! Jeśli opublikujesz to jako dobrze sformatowaną odpowiedź, wybiorę ją.
mike
@bobince - Zaktualizowałem moją odpowiedź o Twoją technikę, ponieważ jeszcze jej nie opublikowałeś. Jeśli utworzysz własną odpowiedź, śmiało i wycofaj moją. :-)
Ben Blank
2
Aha, ostatnia rzecz, będziesz potrzebować „var myspan”, „var newcontent” itp., Aby uniknąć przypadkowego rozlania globals.
bobince
85

Używanie .insertAdjacentHTML()zachowuje detektory zdarzeń i jest obsługiwane przez wszystkie główne przeglądarki . To prosty, jednoliniowy zamiennik .innerHTML.

var html_to_insert = "<p>New paragraph</p>";

// with .innerHTML, destroys event listeners
document.getElementById('mydiv').innerHTML += html_to_insert;

// with .insertAdjacentHTML, preserves event listeners
document.getElementById('mydiv').insertAdjacentHTML('beforeend', html_to_insert);

W 'beforeend'Określa argumentów gdzie w elemencie wstawić zawartość HTML. Dostępne są następujące opcje 'beforebegin', 'afterbegin', 'beforeend', i 'afterend'. Ich odpowiednie lokalizacje to:

<!-- beforebegin -->
<div id="mydiv">
  <!-- afterbegin -->
  <p>Existing content in #mydiv</p>
  <!-- beforeend -->
</div>
<!-- afterend -->
Josh de Leeuw
źródło
Uratowałeś mi dzień!
Tural Asgar
Najlepsze rozwiązanie. Dziękuję Ci!
Dawoodjee
3

Teraz jest rok 2012, a jQuery ma funkcje dołączania i poprzedzania, które robią dokładnie to, dodają zawartość bez wpływu na bieżącą zawartość. Bardzo przydatne.

rakiety są szybkie
źródło
7
.insertAdjacentHTMListnieje od IE4
Esailija
1
@Tynach tak, to ich witryna JavaScript dokumentuje to najlepiej: developer.mozilla.org/en-US/docs/Web/API/Element/ ...
Jordon Bedwell
2
@JordonBedwell, szczerze mówiąc nie mam pojęcia, dlaczego powiedziałem to, co zrobiłem wcześniej. Masz 100% racji. Wydaje mi się, że w tym czasie krótko zajrzałem do tego i nie mogłem go znaleźć, ale ... Szczerze mówiąc, nie mam wymówki. Esailija nawet linki do tej strony.
Tynach
OP nie mówi o jQuery
André Marcondes Teixeira
2

Jako drobna (ale powiązana) pomoc, jeśli używasz biblioteki javascript, takiej jak jquery (v1.3), do manipulacji domem, możesz skorzystać z wydarzeń na żywo, w których skonfigurujesz procedurę obsługi, taką jak:

 $("#myspan").live("click", function(){
  alert('hi');
});

i będzie stosowany do tego selektora przez cały czas podczas jakiejkolwiek manipulacji jQuery. Aby zobaczyć wydarzenia na żywo, zobacz: docs.jquery.com/events/live, aby dowiedzieć się, jak manipulować jquery, patrz: docs.jquery.com/manipulation

Cargowire
źródło
1
OP nie mówi o jQuery
André Marcondes Teixeira
2

Stworzyłem moje znaczniki do wstawiania jako ciąg znaków, ponieważ jest to mniej kodu i łatwiejsze do odczytania niż praca z fantazyjnymi rzeczami dom.

Następnie uczyniłem go innerHTML elementu tymczasowego, aby móc wziąć jedyne dziecko tego elementu i dołączyć do ciała.

var html = '<div>';
html += 'Hello div!';
html += '</div>';

var tempElement = document.createElement('div');
tempElement.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(tempElement.firstChild);
Eneroth3
źródło
2

cos.innerHTML + = 'dodaj cokolwiek chcesz';

to działało dla mnie. Dodałem przycisk do tekstu wejściowego za pomocą tego rozwiązania

KawaiKx
źródło
To była najprostsza metoda, na jaką trafiłem, dziękuję!
demo7up
4
Niszczy wszystkie wydarzenia.
Tural Asgar
1
jak to naprawdę jest rozwiązanie? niszczy wszystkie wydarzenia !!
duński
1

Jest jeszcze jedna alternatywa: używanie setAttributezamiast dodawania detektora zdarzeń. Lubię to:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Demo innerHTML and event listeners</title>
<style>
    div {
        border: 1px solid black;
        padding: 10px;
    }
</style>
</head>
<body>
    <div>
        <span>Click here.</span>
    </div>
    <script>
        document.querySelector('span').setAttribute("onclick","alert('Hi.')");
        document.querySelector('div').innerHTML += ' Added text.';
    </script>
</body>
</html>

Frank Conijn
źródło
1

Tak, jest to możliwe, jeśli powiązasz zdarzenia za pomocą atrybutu tag onclick="sayHi()"bezpośrednio w szablonie, podobnie jak twój <body onload="start()">- to podejście jest podobne do frameworków angular / vue / reag / etc. Możesz także użyć <template>do operowania na „dynamicznym” html, jak tutaj . Nie jest to dyskretny js, ale jest akceptowalny w przypadku małych projektów

function start() {
  mydiv.innerHTML += "bar";
}

function sayHi() {
  alert("hi");
}
<body onload="start()">
  <div id="mydiv" style="border: solid red 2px">
    <span id="myspan" onclick="sayHi()">foo</span>
  </div>
</body>

Kamil Kiełczewski
źródło
0

Utrata obsługi zdarzeń jest, IMO, błędem w sposobie, w jaki Javascript obsługuje DOM. Aby uniknąć tego zachowania, możesz dodać następujące elementy:

function start () {
  myspan = document.getElementById("myspan");
  myspan.onclick = function() { alert ("hi"); };

  mydiv = document.getElementById("mydiv");
  clickHandler = mydiv.onclick;  // add
  mydiv.innerHTML += "bar";
  mydiv.onclick = clickHandler;  // add
}
Tak - ten Jake.
źródło
2
Nie uważam tego za błąd. Wymiana elementu oznacza całkowitą wymianę. Nie powinien dziedziczyć tego, co było wcześniej.
Diodeus - James MacFarlane
Ale nie chcę zastępować elementu; Chcę tylko dołączyć nowe do rodzica.
mike
Gdybyś wymienił element to zgodziłbym się, @Diodeus. To nie .innerHTML ma procedurę obsługi zdarzeń, ale element nadrzędny .innerHtml.
Tak - ten Jake.
2
Właściciel innerHTML nie traci modułów obsługi, gdy zmienia się jego innerHTML, a jedynie wszystko w jego zawartości. A JavaScript nie wie, że dodajesz tylko nowe elementy potomne, ponieważ „x.innerHTML + = y” jest tylko cukrem składniowym dla operacji na łańcuchach „x.innerHTML = x.innerHTML + y”.
bobince
Nie musisz ponownie podłączać mydiv.onclick. Tylko onclick wewnętrznego zakresu jest zastępowany przez innerHTML +=.
Crescent Fresh
0

Możesz to zrobić tak:

var anchors = document.getElementsByTagName('a'); 
var index_a = 0;
var uls = document.getElementsByTagName('UL'); 
window.onload=function()          {alert(anchors.length);};
for(var i=0 ; i<uls.length;  i++)
{
    lis = uls[i].getElementsByTagName('LI');
    for(var j=0 ;j<lis.length;j++)
    {
        var first = lis[j].innerHTML; 
        string = "<img src=\"http://g.etfv.co/" +  anchors[index_a++] + 
            "\"  width=\"32\" 
        height=\"32\" />   " + first;
        lis[j].innerHTML = string;
    }
}
Rishabh gupta
źródło
0

Najłatwiejszym sposobem jest użycie tablicy i wpychanie do niej elementów, a następnie dynamiczne wstawianie kolejnych wartości do tablicy. Oto mój kod:

var namesArray = [];

function myclick(){
    var readhere = prompt ("Insert value");
    namesArray.push(readhere);
    document.getElementById('demo').innerHTML= namesArray;
}
Anoop Anson
źródło
0

Dla dowolnej tablicy obiektów z nagłówkiem i danymi.jsfiddle

https://jsfiddle.net/AmrendraKumar/9ac75Lg0/2/

<table id="myTable" border='1|1'></table>

<script>
  const userObjectArray = [{
    name: "Ajay",
    age: 27,
    height: 5.10,
    address: "Bangalore"
  }, {
    name: "Vijay",
    age: 24,
    height: 5.10,
    address: "Bangalore"
  }, {
    name: "Dinesh",
    age: 27,
    height: 5.10,
    address: "Bangalore"
  }];
  const headers = Object.keys(userObjectArray[0]);
  var tr1 = document.createElement('tr');
  var htmlHeaderStr = '';
  for (let i = 0; i < headers.length; i++) {
    htmlHeaderStr += "<th>" + headers[i] + "</th>"
  }
  tr1.innerHTML = htmlHeaderStr;
  document.getElementById('myTable').appendChild(tr1);

  for (var j = 0; j < userObjectArray.length; j++) {
    var tr = document.createElement('tr');
    var htmlDataString = '';
    for (var k = 0; k < headers.length; k++) {
      htmlDataString += "<td>" + userObjectArray[j][headers[k]] + "</td>"
    }
    tr.innerHTML = htmlDataString;
    document.getElementById('myTable').appendChild(tr);
  }

</script>
AKS
źródło
-5

Jestem leniwym programistą. Nie używam DOM, ponieważ wydaje mi się, że to dodatkowe wpisywanie. Według mnie im mniej kodu, tym lepiej. Oto jak dodałbym „bar” bez zastępowania „foo”:

function start(){
var innermyspan = document.getElementById("myspan").innerHTML;
document.getElementById("myspan").innerHTML=innermyspan+"bar";
}
Lucious
źródło
15
To pytanie dotyczy tego, jak to zrobić bez utraty obsługi zdarzeń, więc twoja odpowiedź nie jest odpowiednia, a przy okazji to właśnie robi + =, co jest o wiele krótsze
wlf
2
To zabawne, że w tej całkowicie błędnej odpowiedzi, która jako uzasadnienie przytacza unikanie dodatkowego wpisywania, około połowa kodu jest zbędna.
JLRishe