28

I have this very simple demo:

function foo() {
    alert('Works!');
}

var inp = document.createElement('input');
inp.onblur = foo;
document.body.appendChild(inp);

See here: http://jsfiddle.net/A7aPA/

As you can see, this works. (Click on the input, then click somewhere else and an alert will pop up.)

However, if I add this line to the JavaScript code:

document.body.innerHTML += '<br>'; 

then the blur handler stops working (and no error is thrown btw).

See here: http://jsfiddle.net/A7aPA/1/

Why is that?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • Reading `innerHTML` will give you a string. So `document.body.innerHTML = document.body.innerHTML;` will remove all your events... – Pacerier May 07 '14 at 13:50

2 Answers2

44

Yes, when you do:

document.body.innerHTML += '<br>'; 

You're really doing:

document.body.innerHTML = (document.body.innerHTML + '<br>'); 

So you're completely destroying and recreating all the content.

user113716
  • 318,772
  • 63
  • 451
  • 440
  • 1
    @patrick So, adding a string to the innerHTML property of an element will destroy all event handlers that were set on the descendants of that element. Holy cow! Now I know `:)` – Šime Vidas Feb 25 '11 at 03:06
  • 2
    @Šime Vidas: Yes, but not only event handlers. It destroys the elements themselves, and therefore the handlers, and any other data that may have been set on a property of the element that doesn't show up as an attribute. [Example](http://jsfiddle.net/A7aPA/8/) – user113716 Feb 25 '11 at 03:11
  • The easier way to accomplish adding the
    is to just create a new
    the same way you created your input and appending that to the document body like you also did for the input.
    – Jeremy Battle Feb 25 '11 at 03:12
  • @patrick Hm, but why didn't the browser make the connection between the onblur property (that was set in the code) and the onblur attribute? I mean, the browser could have stringified the input element as this: ``, it's simple enough... – Šime Vidas Feb 25 '11 at 03:15
  • @Šime Vidas: Because there isn't always a direct map from one to the other. I updated my comment above with an example, but [here's another](http://jsfiddle.net/A7aPA/11/). This one sets the `.value` and `.id` of the input. The `value` is lost, but the `id` updated the actual attribute, and therefore survived. (check console). *Updated my example again. I used an invalid ID*. – user113716 Feb 25 '11 at 03:20
  • @Šime The value is not actually lost. if you look here, http://jsfiddle.net/A7aPA/14/ you will see that in fact value isn't actually being stored as part of the innerHTML, nor is the onblur. – Jeremy Battle Feb 25 '11 at 03:28
  • @Jeremy The value is lost in the innerHTML string. – Šime Vidas Feb 25 '11 at 03:32
  • @patrick It appears that these properties: id, title, className, lang and dir, (from [here](http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-58190037)) have direct mappings, see [here](http://jsfiddle.net/A7aPA/15/). It may very well be that all others have not. – Šime Vidas Feb 25 '11 at 03:34
  • @Šime: Yes it looks that way. The handlers definitely don't map. When you actually set an inline handler, its value becomes the body of an anonymous function that set set on `onclick`. [In this example](http://jsfiddle.net/A7aPA/18/) I used `setAttribute` to set the `onclick` attribute. It survives. But also I logged the `onclick` property to the console. You'll see the function with the value I set for the attribute. – user113716 Feb 25 '11 at 03:41
  • @patrick Interesting. That means that you can make the blur handler survive the innerHTML overwrite if you set it so: `inp.setAttribute('onblur', 'foo();');` instead of so: `inp.onblur = foo;`. `:)` Demo: http://jsfiddle.net/A7aPA/19/ (In this case, foo has to be a global function though.) – Šime Vidas Feb 25 '11 at 03:52
  • @Šime: Absolutely, as long as you don't mind the downsides, like it being global as you pointed out as well as the additional function call. Of course you could namespace it, and do `"myNamespace.foo()"` so it wasn't global. In order to get the fully duplicated experience of the `onclick` property, you'd need to set it more like this `"foo.call( this, event );"` so that you get the proper execution context, and you get your event object. Note that I haven't tested this in all browsers, but it has worked in the ones where I have. – user113716 Feb 25 '11 at 03:59
  • @Šime: However, `inp.setAttribute('onblur', 'foo();');` will not work in older IE (6, 7, some IE 8 modes) because of its broken `setAttribute()` implementation that maps attributes directly to properties. – Tim Down Feb 25 '11 at 09:16
  • @Tim: Thanks for that note. I knew I should have checked that. I thought the IE issue was that it would set the attribute but not the property in some cases. I'll have to mess around with that in IE6/7 one of these days. – user113716 Feb 25 '11 at 14:04
  • By understading `document.body.innerHTML = (document.body.innerHTML + '
    ');` , now I understood why attached events removed. Thankyou this saves my time. It was very basic but I actually forgot.
    – Abhishek Kamal May 04 '20 at 17:56
38

Modifying innerHTML causes the content to be re-parsed and DOM nodes to be recreated, losing the handlers you have attached. Appending elements as in the first example doesn't cause that behavior, so no re-parsing has to occur, since you are modify the DOM tree explicitly.

Another good way to handle this is to use insertAdjacentHTML(). For example:

document.body.insertAdjacentHTML('beforeend', '<br>')
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
Chris Cherry
  • 28,118
  • 6
  • 68
  • 71