JS: innerHTML and DOM aren't cooperating - javascript

I've noticed a funny behavior and was wondering if anybody could shed some light on the reason.
It breaks down like this:
I've added a div and a button using Javascript's `appendChild'.
I add an onclick handler to the button, works fine
I add-assign the innerHTML of the div to add some more content
Button onclick stops working altogether
Here's a sample script:
var D = document.createElement("DIV"), B = document.createElement("BUTTON");
B.innerHTML = "Is it true?";
document.body.appendChild(D);
D.appendChild(B);
B.onclick = function() { alert("Elvis Lives!"); }
At this point, it works fine. Then, add this line:...
D.innerHTML += "What about Tupac?";
...and the button breaks. So, I'm just using appendChild for all elements now.
But -
Why is this happening ("seems like it should work :) ")
Is there a fix (besides appending all my extra elements)?

(this is a lot of educated guessing) I think when you modify D's innerHTML, the dom within D is destroyed and created from the new value. So when the new contents are constructed, you are getting a brand new button, which is not the same node as B. since you set the onclick handler via js and not by an attribute on the original element, the new button does not have that function reference. however B still works as can be demonstrated by calling
B.click();
after you append to innerHTML.
to prevent the thrashing of the dom within D, you can do this instead of using innerHTML:
D.appendChild(document.createTextNode("What about Tupac?"));

innerHTML is a shortcut for creating DOM elements. When you append something to the outer div using innerHTML, this is what happens:
D.innerHTML += "What about Tupac?";
which is the same as,
D.innerHTML = D.innerHTML + "What about Tupac?";
which is the same as,
D.innerHTML = "<button>Is it true?</button>" + "What about Tupac?";
which finally becomes this,
D.innerHTML = "<button>Is it true?</button>What about Tupac?";
Now in the last step, we completely replaced the existing contents of the div, with a new string which contains HTML. As far as the DOM is concerned, it doesn't care whether a user typed the HTML by hand, or it came from calling innerHTML on a DOM node. All it cares about is, is that it has a string of HTML, which must be converted to a DOM. A new button element is created at this time, which is why the onclick stops working - it's not the same element anymore.

Not an answer, just a test page - looks very strange - I can confirm your findings
<script>
window.onload=function() {
var D = document.createElement("DIV"), B = document.createElement("BUTTON");
D.id = "div1"
B.innerHTML = "Is it true?";
B.onclick = function() { alert("Elvis Lives!"); }
D.appendChild(B);
document.body.appendChild(D);
}
</script>
Click<br>
Test
update: Lessons learned - be consistent.
This works as expected
<script>
window.onload=function() {
var D = document.createElement("DIV");
D.id = "div1"
D.innerHTML = '<button onclick="alert(\'Elvis Lives!\')">Elvis</button>'
document.body.appendChild(D);
}
</script>
Click<br>

Related

Create html elements inside tooltip for a chrome extension

I want to create an HTML element (a div) using javascript to use that as a tooltip.
I can create the simple element by doing:
const element = document.createElement("div");
element.id = "myID"
and that works fine...however, I want to add a table (and some other HTML) inside the tooltip, so I was trying to do
element.appendChild(childElement); //where the childElement is another document.createElement("") that contains all the HTML I want.
or:
element.insertAdjacentHTML('afterbegin', '<table></table>');
however, nothing happens. there's no error, but it won't append it either. Am I missing something?
If it matters, this is happening inside the contentScripts.js of a chrome extension I'm building.
EDIT
Full code of div creation:
const element = document.createElement("div");
element.id = "tooltip-creation";
element.classList.add("tooltip");
const childElement = document.createElement("div");
childElement.id = "data";
//I've attempted to do .appendChild, innerHTML, and .insertAdjacentHTML (as can be seen here) and neither works but no error is given.
element.appendChild(childElement);
element.insertAdjacentHTML('afterbegin','<table border="1"><tr><td><strong>OMG</strong></td></tr></table>');
element.innerHTML = "<table border="1"><tr><td><strong>OMG</strong></td></tr></table>";
Separately I have 2 functions that do:
document.addEventListener("mouseover", function(e){
if (e.target.classList.contains("tooltip")) {
createTooltip(e);
}
});
document.addEventListener("mouseout", function(e){
if (e.target.classList.contains("tooltip")) {
removeAllTooltips();
}
});
I think your issue is that you're not actually appending the tooltip to anything. You need to append your element to a node that is already in the DOM. Here is a working example (without any CSS) that I got from your code, the only difference being that I appended the element node to an existing element in the DOM called root.
https://jsfiddle.net/irantwomiles/09o7vuj5/12/

Adding a textbox to div without losing values [duplicate]

This question already has answers here:
InnerHTML append instead of replacing
(3 answers)
Closed 6 years ago.
i've a div container and a button. Whenever i click the button, an empty textbox is added to the div. Now, my problem is whenever i click the button, the textbox is added, but the values of all others are removed.
The function is made like this:
function addTextBox() {
document.getElementById("txtList").innerHTML += "<input type='text'>";
}
I think it help you:
var child = document.createElement('input')
document.getElementById("txtList").appendChild(child);
You could achieve the same thing as the snippet below:
function addTextBox() {
var input = document.createElement("input");
input.type = "text"
document.getElementById("txtList").appendChild(input);
}
document.getElementById("addTxtBoxBtn").addEventListener("click",addTextBox);
<input type="button" id="addTxtBoxBtn" value="add TextBox"/>
<div id="txtList">
</div>
Why you can't achieve the same thing with innerHTML?
This happens because:
The Element.innerHTML property sets or gets the HTML syntax describing the element's descendants.
While the valueof an ipunt element is not an attribute of the element but a property (please have a look here).
If you want to check it in action, please try the following snippet:
function addTextBox() {
var txtList = document.getElementById("txtList");
console.log(txtList.innerHTML);
txtList.innerHTML += "<input type='text'/>" ;
}
document.getElementById("addTxtBoxBtn").addEventListener("click",addTextBox);
<input type="button" id="addTxtBoxBtn" value="add TextBox"/>
<div id="txtList">
</div>
What is happening under the hood here is that when you append the DOM as text using innerHTML you are simply rewriting that section of HTML. Editing your textList innerHTML will execute a new paint of that element and all information will be parsed again. This means you loose your user interaction.
To update your DOM elements successfully there are methods which enable you to do that. namely document.createElement and document.appendChild.
By appending the DOM element as opposed to concatenating the innerHTML(text) your are forcing a limited paint of the specific area. This leaves the rest of the DOM in tact.
Your code here
function addTextBox() {
document.getElementById("txtList").innerHTML += "<input type='text'>";
}
Becomes more like the following
function addTextBox() {
var textEl = document.getElementById("txtList");
var input = document.createElement("input");
input.type = 'text';
textEl.appendChild(input);
}
When you change append to innerHTML as a string, another string gets created (they are immutable). Browser than has to re-render the whole thing.
The other answers show appendChild, but since in your original question you used a string, maybe you want to keep doing so. If that's the case, you can use insertAdjacentHTML with 'beforeend' as first argument.
document
.getElementById('button')
.addEventListener('click', () => {
document.getElementById('txtList')
.insertAdjacentHTML('beforeend', '<input type="text">');
});
JSBin link is here.

Appending new paragraphs

Been stuck on this for a while now, trying to add multiple new p elements to a div but it is just adding the content to the first one instead of creating a new one.
var p = document.createElement("p");
var output = document.getElementById('output');
function on button press
p.appendChild(document.createTextNode("hello"+"\n"));
output.appendChild(p);
Thanks for any help in advance, I need a solution where i'm allowed an infinite amount of new paragraphs until a condition is met.
The problem is that you create only one paragraph but append multiple text nodes in it. Despite on how it looks, output.appendChild(p) doesn't append initial p more then once. In fact, if the element is already in DOM (like in your case afther the first click), appendChild simply moves element to a new location. But in your case new location is the same as the original. So as the result, you only create new text node with every click.
You need to create new HTMLParagraphElement on every click:
document.querySelector('button').onclick = function() {
var p = document.createElement("p");
var output = document.getElementById('output');
p.appendChild(document.createTextNode("hello"+"\n"));
output.appendChild(p);
};
<button>Click</button>
<div id="output"></div>

Document Create Full Element

I'm making a little app, which has to append 3 elements to another element, by using this code:
var MyElem1= document.createElement("div");
ParentElem.appendChild(MyElem1);
This works just fine, but i was wondering if there is a way to create a full element, like this for example:
var MyElem1= document.createElement('<div style="some-styling: here;">Some InnerHtml Here</div>');
ParentElem.appendChild(MyElem1);
I know i can add those properties to the element after i create it, but i'm hopping there's a way to do it inline like that (Something that works cross-browser).
I saw on W3Schools (yes i know i should stop using it) the createElement function requires only the element type (div, span, button, etc...).
You could create a dummy container and create all elements you want inside it by replacing its innerHTML property, and then getting the .firstChild.
Here is a reusable function for it
var elementFactory = (function (){
var dummy = document.createElement('div');
return function(outerHtml){
var node;
dummy.innerHTML = outerHtml;
node = dummy.firstChild;
dummy.removeChild(node);
return node;
}
})();
and use it like this
var MyElem1 = elementFactory('<div style="some-styling: here;">Some InnerHtml Here</div>'),
MyElem2 = elementFactory('<div style="some-other-styling: here;">Some Other InnerHtml Here</div>');
Demo at http://jsfiddle.net/5De3p/1/

Javascript not being called from hyperlink

I am using Google Apps Script to create a web app that contains several hyperlinks. When a hyperlink is clicked, it is supposed to trigger an alert box (just filler code to see if JS function is being called). My hyperlinks are being generated using this javascript function:
function updateAlertList(results) {
var div = document.getElementById('output');
div.innerHTML = '';
for (var i=0;i<results.length;i++)
{
div.innerHTML += '<p><a class="ra-button" href="javascript:void(0)" onclick="myJsFunc()">' + results[i].alertName + '</a></p>';
}
}
and the JS function that generates the alert is:
function myJsFunc(){
alert('this function was called');
}
For some reason, only the LAST hyperlink in the list of hyperlinks will trigger the JS function called myJsFunc. Whenever I click on any of the other hyperlinks that are earlier in the list, the JS function is not called. When I look in the console in Chrome Developer tools, I see the error "Cannot read property 'plugin_dispatchEvent___' of undefined"
Anybody know why the very last hyperlink in my list of hyperlinks works.. but the earlier ones don't?
I would change your code to this:
function updateAlertList(results) {
var div = document.getElementById('output');
div.innerHTML = '';
var alrtInnrHTML = '';
for (var i=0;i<results.length;i++) {
alrtInnrHTML += '<p><a class="ra-button" href="javascript:void(0)" onclick="myJsFunc()">' + results[i].alertName + '</a></p>';
console.log('alrtInnrHTML: ' + alrtInnrHTML);
}
}
div.innerHTML = alrtInnrHTML;
Please note that I created a variable alrtInnrHTML, and using += on the variable, not the innerHTML.
You are trying to use += on DOM manipulation instead of a variable. Set the HTML text string first, then inject the entire string just one time. Right now you are injecting HTML into the same DIV over and over and over again. Do all the links show up? In any case, even if your logic is working, I would change the logic to what I am suggesting, check that the final HTML content is correct with a console.log() output, and then inject the HTML all at once.
You would actually be better off adding individual <p> and <a> tags in each loop, and adding HTML in each loop rather than trying to construct a string of HTML.
You might want to do some reading about:
insertRow
insertCell
setAttribute
textContent
createTextNode
appendChild
createElement
With those methods you could avoid writing long HTML strings for the most part.
For example, you could create a table, with a new row for every result.
for (var i=0;i<results.length;i++) {
window.row1 = tblAlertList.insertRow(-1);
window.cell1 = row1.insertCell(0);
row1.setAttribute("class", "ra-button");
row1.innerHTML = <p><a class="ra-button" href="javascript:void(0)" onclick="myJsFunc()">' + results[i].alertName + '</a></p>';
}
That above code is just to give you an idea of what you might do, it's not working code.
Instead of creating a table, you could create an unordered list: <ul>
div.innerHTML = "<ul><a></a></ul>";
Then append a child to the last node in the list:
var node=document.createElement("LI");
var textnode=document.createTextNode("New Alert");
node.appendChild(textnode);
document.getElementById("myList").appendChild(node);

Categories

Resources