Est-il possible d'ajouter à innerHTML sans détruire les écouteurs d'événements des descendants?

112

Dans l'exemple de code suivant, j'attache un onclickgestionnaire d'événements à la plage contenant le texte «foo». Le gestionnaire est une fonction anonyme qui affiche un fichier alert().

Cependant, si j'assigne au nœud parent innerHTML, ce onclickgestionnaire d'événements est détruit - cliquer sur "foo" ne parvient pas à afficher la boîte d'alerte.

Est-ce réparable?

<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
la source
Cette question est liée au problème «l'ajout de nouveaux champs à un formulaire efface la saisie utilisateur». La réponse choisie résout très bien ces deux problèmes.
MattT
La délégation d'événements peut être utilisée pour résoudre ce problème.
dasfdsa

Réponses:

127

Malheureusement, l'affectation à innerHTMLprovoque la destruction de tous les éléments enfants, même si vous essayez d'ajouter. Si vous souhaitez conserver les nœuds enfants (et leurs gestionnaires d'événements), vous devrez utiliser les fonctions DOM :

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

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

Edit: la solution de Bob, à partir des commentaires. Postez votre réponse, Bob! Obtenez du crédit pour cela. :-)

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
la source
Existe-t-il un substitut qui peut ajouter un blob arbitraire de HTML?
mike le
64
newcontent = document.createElement ('div'); newcontent.innerHTML = arbitraire_blob; while (newcontent.firstChild) mydiv.appendChild (newcontent.firstChild);
bobince le
Bien, Bob! Si vous publiez cela comme une réponse bien formatée, je la sélectionnerai.
mike le
@bobince - J'ai mis à jour ma réponse avec votre technique, puisque vous ne l'avez pas encore postée vous-même. Si vous créez votre propre réponse, allez-y et annulez la mienne. :-)
Ben Blank
2
Oh, une dernière chose, vous voudrez «var myspan», «var newcontent» etc. pour éviter de renverser accidentellement des globaux.
bobince le
85

L'utilisation .insertAdjacentHTML()préserve les écouteurs d'événements et est prise en charge par tous les principaux navigateurs . C'est un simple remplacement d'une ligne pour .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);

L' 'beforeend'argument spécifie où dans l'élément insérer le contenu HTML. Les options sont 'beforebegin', 'afterbegin', 'beforeend'et 'afterend'. Leurs emplacements correspondants sont:

<!-- beforebegin -->
<div id="mydiv">
  <!-- afterbegin -->
  <p>Existing content in #mydiv</p>
  <!-- beforeend -->
</div>
<!-- afterend -->
Josh de Leeuw
la source
Tu as sauvé ma journée!
Tural Asgar
Meilleure solution. Je vous remercie!
Dawoodjee
3

Maintenant, nous sommes en 2012 et jQuery a des fonctions d'ajout et de préfixe qui font exactement cela, ajouter du contenu sans affecter le contenu actuel. Très utile.

fusée rapide
la source
7
.insertAdjacentHTMLexiste depuis IE4
Esailija
1
@Tynach oui, c'est leur site JavaScript qui le documente le mieux: developer.mozilla.org/en-US/docs/Web/API/Element/…
Jordon Bedwell
2
@JordonBedwell, honnêtement, je n'ai aucune idée pourquoi j'ai dit ce que j'ai fait avant. Vous avez raison à 100%. J'ai l'impression qu'à ce moment-là, je l'ai brièvement examiné et je n'ai pas pu le trouver, mais ... Honnêtement, je n'ai aucune excuse. Esailija a même des liens vers cette page.
Tynach
OP ne parle pas de jQuery
André Marcondes Teixeira
2

En guise d'asside légère (mais liée), si vous utilisez une bibliothèque javascript telle que jquery (v1.3) pour faire votre manipulation dom, vous pouvez utiliser des événements en direct dans lesquels vous configurez un gestionnaire comme:

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

et il sera appliqué à ce sélecteur à tout moment pendant tout type de manipulation de jquery. Pour les événements en direct, voir: docs.jquery.com/events/live pour la manipulation de jquery, voir: docs.jquery.com/manipulation

Cargowire
la source
1
OP ne parle pas de jQuery
André Marcondes Teixeira
2

J'ai créé mon balisage à insérer sous forme de chaîne car c'est moins de code et plus facile à lire que de travailler avec les trucs sophistiqués.

Ensuite, je l'ai fait innerHTML d'un élément temporaire juste pour que je puisse prendre le seul et unique enfant de cet élément et l'attacher au corps.

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

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

something.innerHTML + = 'ajoutez ce que vous voulez';

cela a fonctionné pour moi. J'ai ajouté un bouton à un texte d'entrée en utilisant cette solution

KawaiKx
la source
C'était la méthode la plus simple que je connaisse, merci!
demo7up
4
Il détruit tous les événements.
Tural Asgar du
1
comment est-ce vraiment une solution? ça détruit tous les événements !!
danois
1

Il existe une autre alternative: utiliser setAttributeplutôt que d'ajouter un écouteur d'événements. Comme ça:

<!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
la source
1

Oui, c'est possible si vous liez des événements en utilisant l'attribut de balise onclick="sayHi()"directement dans un modèle similaire à votre <body onload="start()">- cette approche similaire aux frameworks angular / vue / react / etc. Vous pouvez également utiliser <template>pour opérer sur du HTML «dynamique» comme ici . Ce n'est pas un js discret strict mais il est acceptable pour les petits projets

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
la source
0

La perte des gestionnaires d'événements est, IMO, un bogue dans la façon dont Javascript gère le DOM. Pour éviter ce problème, vous pouvez ajouter les éléments suivants:

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
}
Oui - ce Jake.
la source
2
Je ne considère pas cela comme un bug. Le remplacement d'un élément signifie que vous le remplacez complètement. Il ne devrait pas hériter de ce qui existait auparavant.
Diodeus - James MacFarlane
Mais je ne veux pas remplacer un élément; Je veux juste en ajouter de nouveaux au parent.
mike le
Si vous remplaçiez l'élément, je serais d'accord, @Diodeus. Ce n'est pas le .innerHTML qui a le gestionnaire d'événements, mais le parent du .innerHtml.
Oui - ce Jake.
2
Le propriétaire de innerHTML ne perd pas de gestionnaires lorsque son innerHTML est modifié, seulement quoi que ce soit dans son contenu. Et JavaScript ne sait pas que vous ajoutez uniquement de nouveaux enfants, car «x.innerHTML + = y» n'est que du sucre syntaxique pour l'opération de chaîne «x.innerHTML = x.innerHTML + y».
bobince le
Vous n'avez pas besoin de vous reconnecter mydiv.onclick. Seul le clic de la plage interne est remplacé par innerHTML +=.
Crescent Fresh
0

Vous pouvez le faire comme ceci:

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
la source
0

Le moyen le plus simple est d'utiliser un tableau et d'y insérer des éléments, puis d'insérer dynamiquement les valeurs suivantes du tableau dans le tableau. Voici mon code:

var namesArray = [];

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

Pour tout tableau d'objets avec en-tête et données.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
la source
-5

Je suis un programmeur paresseux. Je n'utilise pas DOM car cela semble être une saisie supplémentaire. Pour moi, moins il y a de code, mieux c'est. Voici comment ajouter "bar" sans remplacer "foo":

function start(){
var innermyspan = document.getElementById("myspan").innerHTML;
document.getElementById("myspan").innerHTML=innermyspan+"bar";
}
lucieux
la source
15
Cette question demande comment faire cela sans perdre les gestionnaires d'événements, donc votre réponse n'est pas pertinente, et c'est ce que + = fait, ce qui est beaucoup plus court
wlf
2
Il est hilarant que dans cette réponse complètement fausse qui cite éviter de taper supplémentaire comme justification, environ la moitié du code est redondant.
JLRishe