views:

4644

answers:

4

In the following example code, I attach an onclick handler to the span containing the text "foo". The handler is an anonymous function that pops up an alert().

However, if I append to the parent node's innerHTML, this onclick handler gets destroyed -- clicking "foo" fails to pop up the alert box.

Is this fixable?

<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>
+9  A: 

Unfortunately, assignment to innerHTML causes the destruction of all child elements, even if you're trying to append. If you want to preserve child nodes (and their event handlers), you'll need to use DOM functions:

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

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

Edit: Bob's solution, from the comments. Post your answer, Bob! Get credit for it. :-)

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
Is there a substitute that can append an arbitrary blob of HTML?
mike
newcontent= document.createElement('div'); newcontent.innerHTML= arbitrary_blob; while (newcontent.firstChild) mydiv.appendChild(newcontent.firstChild);
bobince
Nice, Bob! If you post that as a well-formatted answer, I'll select it.
mike
@bobince — I've updated my answer with your technique, since you haven't posted it yet yourself. If you create your own answer, go ahead and roll mine back. :-)
Ben Blank
@Ben: you don't need the call to newcontent.removeChild(). Bob's original comment was correct.
Crescent Fresh
@crescentfresh — If you don't remove the child node from newcontent, `while (newcontent.firstChild)` loops forever. Try it.
Ben Blank
Hmm… I take that back. It *shouldn't* loop — I have no idea what was going wrong the first time I tested it. :-/
Ben Blank
Oh, one last thing, you'll want “var myspan”, “var newcontent” etc. to avoid accidentally spilling globals.
bobince
Ben Blank
A: 

Losing event handlers is, IMO, a bug in the way Javascript handles the DOM. To avoid this behavior, you can add the following:

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
}
Jekke
I don't consider this a bug. Replacing an element means you completely replace it. It should not inherit what was there before.
Diodeus
But I don't want to replace an element; I just want to append new ones to the parent.
mike
If you were replacing the element, I would agree, @Diodeus. It's not the .innerHTML that has the event handler, but the .innerHtml's parent.
Jekke
The innerHTML's owner does not lose handlers when its innerHTML is changed, only anything in its contents. And JavaScript doesn't know that you are only appending new children, since “x.innerHTML+= y” is only syntactical sugar for the string operation “x.innerHTML= x.innerHTML+y”.
bobince
You don't need to re-attach `mydiv.onclick`. Only the inner span's onclick is overridden by `innerHTML +=`.
Crescent Fresh
A: 

As a slight (but related) asside, if you use a javascript library such as jquery (v1.3) to do your dom manipulation you can make use of live events whereby you set up a handler like:

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

and it will be applied to that selector at all times during any kind of jquery manipulation. For live events see: docs.jquery.com/events/live for jquery manipulation see: docs.jquery.com/manipulation

Cargowire
A: 

I'm a lazy programmer. I don't use DOM because it seems like extra typing. To me, the less code the better. Here's how I would add "bar" without replacing "foo":

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