¿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>
Miguel
fuente
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);
    }
}
Ben Blank
fuente
¿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 -->
Josh de Leeuw
fuente
¡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.

cohetes rápidos
fuente
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

Cargowire
fuente
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);
Eneroth3
fuente
2

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

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

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

Frank Conijn
fuente
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>

Kamil Kiełczewski
fuente
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
}
Sí, ese Jake.
fuente
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;
    }
}
Rishabh gupta
fuente
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;
}
Anoop Anson
fuente
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>
AKS
fuente
-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";
}
lucious
fuente
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