¿Es posible agregar a innerHTML sin destruir los oyentes de eventos de los descendientes?


112

En el siguiente código de ejemplo, adjunto un onclickcontrolador de eventos al intervalo que contiene el texto "foo". El controlador es una función anónima que muestra un archivo alert().

Sin embargo, si lo asigno al nodo principal innerHTML, este onclickcontrolador de eventos se destruye; al hacer clic en "foo" no aparece el cuadro de alerta.

¿Es esto reparable?

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

Esta pregunta está relacionada con el problema de 'agregar nuevos campos a un formulario borra la entrada del usuario'. La respuesta seleccionada soluciona ambos problemas muy bien.
MattT

La delegación de eventos se puede utilizar para abordar este problema.
dasfdsa

Respuestas:


126

Desafortunadamente, la asignación a innerHTMLcausa la destrucción de todos los elementos secundarios, incluso si está intentando agregar. Si desea conservar los nodos secundarios (y sus controladores de eventos), deberá usar las funciones DOM :

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

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

Editar: la solución de Bob, de los comentarios. ¡Publica tu respuesta, Bob! Obtenga crédito por ello. :-)

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

¿Existe un sustituto que pueda agregar un blob arbitrario de HTML?
Mike

64
contenido nuevo = document.createElement ('div'); newcontent.innerHTML = arbitrary_blob; while (newcontent.firstChild) mydiv.appendChild (newcontent.firstChild);
Bobince

¡Bien, Bob! Si publica eso como una respuesta bien formateada, la seleccionaré.
Mike

@bobince: actualicé mi respuesta con su técnica, ya que aún no la ha publicado usted mismo. Si crea su propia respuesta, siga adelante y deshaga la mía. :-)
Ben Blank

2
Oh, una última cosa, querrás "var myspan", "var newcontent", etc. para evitar derramar globales accidentalmente.
Bobince

85

El uso .insertAdjacentHTML()conserva los detectores de eventos y es compatible con todos los navegadores principales . Es un simple reemplazo de una línea para .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);

El 'beforeend'argumento especifica en qué parte del elemento insertar el contenido HTML. Las opciones son 'beforebegin', 'afterbegin', 'beforeend', y 'afterend'. Sus ubicaciones correspondientes son:

<!-- beforebegin -->
<div id="mydiv">
  <!-- afterbegin -->
  <p>Existing content in #mydiv</p>
  <!-- beforeend -->
</div>
<!-- afterend -->

¡Salvaste mi día!
Tural Asgar

Mejor solución. ¡Gracias!
Dawoodjee

3

Ahora, estamos en 2012, y jQuery tiene funciones de agregar y anteponer que hacen exactamente esto, agregar contenido sin afectar el contenido actual. Muy útil.


7
.insertAdjacentHTMLha existido desde IE4
Esailija

1
@Tynach sí, es su sitio JavaScript el que lo documenta mejor: developer.mozilla.org/en-US/docs/Web/API/Element/…
Jordon Bedwell

2
@JordonBedwell, honestamente, no tengo idea de por qué dije lo que dije antes. Tienes toda la razón. Siento que en ese momento lo miré brevemente y no pude encontrarlo, pero ... Honestamente, no tengo excusa. Esailija incluso tiene enlaces a esa página.
Tynach

OP no habla de jQuery
André Marcondes Teixeira

2

Como ayuda leve (pero relacionada), si usa una biblioteca javascript como jquery (v1.3) para realizar su manipulación dom, puede hacer uso de eventos en vivo mediante los cuales configura un controlador como:

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

y se aplicará a ese selector en todo momento durante cualquier tipo de manipulación de jquery. Para eventos en vivo, consulte: docs.jquery.com/events/live para la manipulación de jquery, consulte: docs.jquery.com/manipulation


1
OP no habla de jQuery
André Marcondes Teixeira

2

Creé mi marcado para insertarlo como una cadena, ya que es menos código y más fácil de leer que trabajar con las cosas elegantes de dom.

Luego lo hice innerHTML de un elemento temporal solo para poder tomar el único hijo de ese elemento y adjuntarlo al cuerpo.

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

var tempElement = document.createElement('div');
tempElement.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(tempElement.firstChild);

2

something.innerHTML + = 'agrega lo que quieras';

funcionó para mí. Agregué un botón a un texto de entrada usando esta solución


Este fue el método más simple que he encontrado, ¡gracias!
demo7up

4
Destruye todos los eventos.
Tural Asgar

1
¿Cómo es realmente una solución? destruye todos los eventos !!
Danés

1

Hay otra alternativa: usar en setAttributelugar de agregar un detector de eventos. Me gusta esto:

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


1

Sí, es posible si enlaza eventos usando el atributo de etiqueta onclick="sayHi()"directamente en una plantilla similar a la suya <body onload="start()">: este enfoque es similar a los marcos angular / vue / react / etc. También puede utilizar <template>para operar en html 'dinámico' como aquí . No es un js estrictamente discreto, sin embargo, es aceptable para proyectos pequeños

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>


0

La pérdida de controladores de eventos es, en mi opinión, un error en la forma en que Javascript maneja el DOM. Para evitar este comportamiento, puede agregar lo siguiente:

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
}

2
No considero esto un error. Reemplazar un elemento significa reemplazarlo completamente. No debería heredar lo que había antes.
Diodeus - James MacFarlane

Pero no quiero reemplazar un elemento; Solo quiero agregar nuevos al padre.
Mike

Si estuvieras reemplazando el elemento, estaría de acuerdo, @Diodeus. No es el .innerHTML el que tiene el controlador de eventos, sino el padre .innerHtml.
Sí, ese Jake.

2
El propietario del innerHTML no pierde controladores cuando se cambia su innerHTML, solo cualquier cosa en su contenido. Y JavaScript no sabe que solo está agregando nuevos hijos, ya que "x.innerHTML + = y" es solo azúcar sintáctico para la operación de cadena "x.innerHTML = x.innerHTML + y".
Bobince

No es necesario volver a adjuntar mydiv.onclick. Solo se anula el clic del intervalo interno innerHTML +=.
Crescent Fresh

0

Podrías hacerlo así:

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

0

La forma más fácil es usar una matriz e insertar elementos en ella y luego insertar los valores subsiguientes de la matriz en la matriz de forma dinámica. Aquí está mi código:

var namesArray = [];

function myclick(){
    var readhere = prompt ("Insert value");
    namesArray.push(readhere);
    document.getElementById('demo').innerHTML= namesArray;
}

0

Para cualquier matriz de objetos con encabezado y datos.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>

-5

Soy un programador vago. No uso DOM porque parece una escritura adicional. Para mí, cuanto menos código, mejor. Así es como agregaría "bar" sin reemplazar "foo":

function start(){
var innermyspan = document.getElementById("myspan").innerHTML;
document.getElementById("myspan").innerHTML=innermyspan+"bar";
}

15
Esta pregunta es cómo hacer esto sin perder controladores de eventos, por lo que su respuesta no es relevante, y por cierto, esto es lo que hace + =, que es mucho más corto
wlf

2
Es gracioso que en esta respuesta completamente incorrecta que cita evitar la escritura adicional como justificación, aproximadamente la mitad del código es redundante.
JLRishe
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.