É possível anexar ao innerHTML sem destruir os ouvintes de eventos dos descendentes?

112

No código de exemplo a seguir, anexei um onclickmanipulador de eventos ao intervalo que contém o texto "foo". O manipulador é uma função anônima que exibe um alert().

No entanto, se eu atribuir ao nó pai innerHTML, esteonclick manipulador de eventos será destruído - clicar em "foo" não abre a caixa de alerta.

Isso pode ser corrigido?

<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>
Mike
fonte
Esta questão está relacionada ao problema 'acrescentar novos campos a um formulário apaga a entrada do usuário'. A resposta selecionada corrige esses dois problemas muito bem.
MattT
A delegação de eventos pode ser usada para resolver esse problema.
dasfdsa

Respostas:

126

Infelizmente, a atribuição a innerHTMLcausa a destruição de todos os elementos filhos, mesmo se você estiver tentando anexar. Se você quiser preservar os nós filhos (e seus manipuladores de eventos), precisará usar as funções DOM :

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

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

Edit: solução de Bob, a partir dos comentários. Publique sua resposta, Bob! Receba crédito por isso. :-)

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
fonte
Existe um substituto que pode anexar um blob arbitrário de HTML?
mike
64
newcontent = document.createElement ('div'); newcontent.innerHTML = arbitrary_blob; while (newcontent.firstChild) mydiv.appendChild (newcontent.firstChild);
bobince
Legal, Bob! Se você postar isso como uma resposta bem formatada, irei selecioná-la.
mike
@bobince - Eu atualizei minha resposta com sua técnica, já que você ainda não a postou. Se você criar sua própria resposta, vá em frente e reverta a minha. :-)
Ben Blank
2
Ah, uma última coisa, você vai querer “var myspan”, “var newcontent” etc. para evitar o derramamento acidental de globais.
bobince
85

O uso .insertAdjacentHTML()preserva ouvintes de eventos e é compatível com todos os principais navegadores . É uma substituição simples de uma linha 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);

O 'beforeend'argumento especifica onde no elemento inserir o conteúdo HTML. As opções são 'beforebegin', 'afterbegin', 'beforeend', e 'afterend'. Seus locais correspondentes são:

<!-- beforebegin -->
<div id="mydiv">
  <!-- afterbegin -->
  <p>Existing content in #mydiv</p>
  <!-- beforeend -->
</div>
<!-- afterend -->
Josh de Leeuw
fonte
Você salvou meu dia!
Tural Asgar
Melhor solução. Obrigado!
Dawoodjee
3

Agora, estamos em 2012 e o jQuery tem funções anexar e anexar que fazem exatamente isso, adicionar conteúdo sem afetar o conteúdo atual. Muito útil.

foguetes descalços
fonte
7
.insertAdjacentHTMLexiste desde IE4
Esailija
1
@Tynach sim, é o site JavaScript deles que documenta melhor: developer.mozilla.org/en-US/docs/Web/API/Element/…
Jordon Bedwell
2
@JordonBedwell, Sinceramente não tenho ideia de por que disse o que disse antes. Você está 100% certo. Eu sinto que na hora eu olhei rapidamente para ele e não consegui achar, mas ... Honestamente eu não tenho desculpa. Esailija até links para essa página.
Tynach,
OP não está falando de jQuery
André Marcondes Teixeira
2

Como uma pequena (mas relacionada) asside, se você usar uma biblioteca javascript como jquery (v1.3) para fazer sua manipulação dom, você pode fazer uso de eventos ao vivo por meio dos quais você configura um manipulador como:

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

e será aplicado a esse seletor em todos os momentos durante qualquer tipo de manipulação do jquery. Para eventos ao vivo, consulte: docs.jquery.com/events/live para manipulação do jquery, consulte: docs.jquery.com/manipulation

Cargowire
fonte
1
OP não fala em jQuery
André Marcondes Teixeira
2

Eu criei minha marcação para inserir como string, já que é menos código e mais fácil de ler do que trabalhar com as coisas sofisticadas do dom.

Em seguida, criei um innerHTML de um elemento temporário apenas para poder pegar o único filho desse elemento e anexar ao corpo.

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

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

algo.innerHTML + = 'adicione o que quiser';

funcionou para mim. Eu adicionei um botão a um texto de entrada usando esta solução

KawaiKx
fonte
Este foi o método mais simples que eu encontrei obrigado!
demo7up
4
Ele destrói todos os eventos.
Tural Asgar
1
como isso é realmente uma solução? isso destrói todos os eventos !!
dinamarquês de
1

Existe outra alternativa: usar em setAttributevez de adicionar um ouvinte de evento. Como isso:

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

Sim, é possível se você vincular eventos usando o atributo tag onclick="sayHi()"diretamente em um modelo semelhante ao seu <body onload="start()">- essa abordagem é semelhante ao framework angular / vue / react / etc. Você também pode usar <template>para operar em html 'dinâmico' como aqui . Não é estritamente discreto, no entanto, é aceitável para pequenos projetos

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

A perda de manipuladores de eventos é, IMO, um bug na maneira como o Javascript lida com o DOM. Para evitar esse comportamento, você pode adicionar o seguinte:

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
}
Sim - aquele Jake.
fonte
2
Não considero isso um bug. Substituir um elemento significa substituí-lo completamente. Não deve herdar o que estava lá antes.
Diodeus - James MacFarlane
Mas não quero substituir um elemento; Eu só quero acrescentar novos ao pai.
mike
Se você estivesse substituindo o elemento, eu concordaria, @Diodeus. Não é o .innerHTML que tem o manipulador de eventos, mas o pai do .innerHtml.
Sim - aquele Jake.
2
O proprietário do innerHTML não perde manipuladores quando seu innerHTML é alterado, apenas qualquer coisa em seu conteúdo. E o JavaScript não sabe que você está apenas acrescentando novos filhos, já que “x.innerHTML + = y” é apenas um açúcar sintático para a operação de string “x.innerHTML = x.innerHTML + y”.
bobince
Você não precisa reconectar mydiv.onclick. Apenas o onclick do span interno é substituído por innerHTML +=.
Crescent Fresh
0

Você poderia fazer assim:

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

A maneira mais fácil é usar uma matriz e inserir elementos nela e, em seguida, inserir os valores subsequentes da matriz na matriz dinamicamente. Aqui está o meu código:

var namesArray = [];

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

Para qualquer matriz de objeto com cabeçalho e dados.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
fonte
-5

Sou um programador preguiçoso. Não uso o DOM porque parece uma digitação extra. Para mim, quanto menos código, melhor. Veja como eu adicionaria "bar" sem substituir "foo":

function start(){
var innermyspan = document.getElementById("myspan").innerHTML;
document.getElementById("myspan").innerHTML=innermyspan+"bar";
}
lúcido
fonte
15
Esta pergunta está perguntando como fazer isso sem perder manipuladores de eventos, então sua resposta não é relevante, e a propósito, é isso que + = faz, que é bem mais curto
wlf
2
É hilário que, nesta resposta completamente errada que cita como justificativa evitar a digitação extra, cerca de metade do código seja redundante.
JLRishe de