Is it possible to append to innerHTML without destroying descendants' event listeners?
JavascriptHtmlInnerhtmlDom EventsJavascript Problem Overview
In the following example code, I attach an onclick
event handler to the span containing the text "foo". The handler is an anonymous function that pops up an alert()
.
However, if I assign to the parent node's innerHTML
, this onclick
event 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>
Javascript Solutions
Solution 1 - Javascript
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);
}
}
Solution 2 - Javascript
Using .insertAdjacentHTML()
preserves event listeners, and is supported by all major browsers. It's a simple one-line replacement for .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);
The 'beforeend'
argument specifies where in the element to insert the HTML content. Options are 'beforebegin'
, 'afterbegin'
, 'beforeend'
, and 'afterend'
. Their corresponding locations are:
<!-- beforebegin -->
<div id="mydiv">
<!-- afterbegin -->
<p>Existing content in #mydiv</p>
<!-- beforeend -->
</div>
<!-- afterend -->
Solution 3 - Javascript
Now, it is 2012, and jQuery has append and prepend functions that do exactly this, add content without effecting current content. Very useful.
Solution 4 - Javascript
I created my markup to insert as a string since it's less code and easier to read than working with the fancy dom stuff.
Then I made it innerHTML of a temporary element just so I could take the one and only child of that element and attach to the body.
var html = '<div>';
html += 'Hello div!';
html += '</div>';
var tempElement = document.createElement('div');
tempElement.innerHTML = html;
document.getElementsByTagName('body')[0].appendChild(tempElement.firstChild);
Solution 5 - Javascript
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
Solution 6 - Javascript
There is another alternative: using setAttribute
rather than adding an event listener. Like this:
<!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>
Solution 7 - Javascript
Yes it is possible if you bind events using tag attribute onclick="sayHi()"
directly in template similar like your <body onload="start()">
- this approach similar to frameworks angular/vue/react/etc. You can also use <template>
to operate on 'dynamic' html like here. It is not strict unobtrusive js however it is acceptable for small projects
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>
Solution 8 - Javascript
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
}
Solution 9 - Javascript
The easiest way is to use an array and push elements into it and then insert the array subsequent values into the array dynamically. Here is my code:
var namesArray = [];
function myclick(){
var readhere = prompt ("Insert value");
namesArray.push(readhere);
document.getElementById('demo').innerHTML= namesArray;
}
Solution 10 - Javascript
You could do it like this:
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;
}
}
Solution 11 - Javascript
something.innerHTML += 'add whatever you want';
it worked for me. I added a button to an input text using this solution
Solution 12 - Javascript
For any object array with header and data.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>
Solution 13 - Javascript
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";
}