JS: Avoiding modifications of ".innerHTML" - javascript

I have some <div> in which I all the time add new objects.
These objects are assigned with listeners.
The problem is that when I add these new objects using .innerHTML, the previous listeners get lost.
Is it possible to create a JS string which represents an HTML object, and to append it as a child without .innerHTML += ... ?
I'll give an example:
var line_num = 0;
function addTextLine(line) {
var lineId = "line_" + line_num;
var lineHtml = "<p id = '" + lineId + "'>" + line + "</p>";
document.getElementById("some_div_id").innerHTML += lineHtml;
document.getElementById(line_id).addEventListener("click", function() {
alert("hello");
});
line_num += 1;
}
The modification of innerHTML of some_dive_id, removes the event listeners of the old <p> objects.
So - is it possible to convert the <p> HTML string into an object, and thus to append it to the some_div_id without modifying its .innerHTML ?

Your problem is that innerHtml erases then recreates the current DOM node; that's why you lose you event listeners.
you can insert your html with insertAdjacentHtml
document.getElementById("some_div_id").insertAdjacentHTML('afterbegin', lineHtml );
the afterbegin parameter assure the inserted html will be a child of your current node.
Look for more infos here: Element.insertAdjacentHTML()

Create the element and append it
var p = document.createElement("p");
p.innerHTML = line;
p.id = line_id; // or p.setAttribute("id", line_id);
p.addEventListener("click", function(){ });
document.getElementById("foo").appendChild(p);
Another option can be to create an element, and set the innerHTML and read the element from there. (First option is better)
var div = document.createElement("div");
div.innerHTML = lineHtml;
//now you can either select the children and append it or append the div.

use element.appendChild().
Your code is not working because everytime you use innerHtml += 'Something' you are removing anything inside that particular element and inserting old content with added string.
Instead you can create element with function and append it to parent element.
Rewriting your code should be:
var line_num = 0;
function addTextLine(line) {
var line = document.createElement('p');
line.id = "line_" + line_num;
line.textContent = line;
line.addEventListener("click", function() {
alert("hello");
});
document.getElementById("some_div_id").appendChild(line);
line_num += 1;
}

Related

change events stop working when a new change event is added to a different element in JS

I am programmatically generating some HTML, and trying to add a listener for a change event the elements.
This works fine for the first object, but as soon as I add the second object the first one stops firing the event.
In the code example below you'll see the updateLabel function only fires for the last object created. I need it to fire for all of the objects.
I have tried with .onchange, and with an event listener, but get the same results.
Any help much appreciated.
<html>
<body>
<div id="main">
<!--this is where all the generated HTML goes -->
</div>
</body>
<script>
mainDomElement = document.querySelector("#main");
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count;
newHTML = '<input class="accordionLabel" type="text" id="' + labelId + '" value="' + labelId + '"/>'
currentHTML = mainDomElement.innerHTML
mainDomElement.innerHTML = newHTML + currentHTML
labelDomObj = document.querySelector('#' + labelId);
//labelDomObj.addEventListener("change", updateLabel);
labelDomObj.onchange = function(event){updateLabel(event)}
}
function updateLabel(event)
{
alert(event.target.value);
}
</script>
</html>
It may be best to take a different approach when creating and adding DOM elements. Try this.
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count,
newHTML = document.createElement('input');
newHTML.type = 'text';
newHTML.value = labelId;
newHTML.id = labelId;
newHTML.addEventListener('onchange', updateLabel, false);
mainDomElement.appendChild(newHTML);
}
your code explanation
// this takes the main DOM element and stores a copy of it in the variable.
currentHTML = mainDomElement.innerHTML
/*
This is taking the innerHTML property of the main DOM element. It is then
trying to concatenate the newly created DOM to that stored in the mainDomElement
variable. I don’t think this is what you want.
*/
mainDomElement.innerHTML = newHTML + currentHTML
// this is trying to select an element from the DOM that does not exist yet.
labelDomObj = document.querySelector('#' + labelId);
// This is trying to add an event listener to an element that does not exist.
labelDomObj.onchange = function(event){updateLabel(event)}
You are also missing sim icons from your variable declarations.
This looks like an issue with closures, where initializing var count = 0 within the for loop ultimately results in only the last value getting bound to the event handler.
I believe moving the initialization outside of the loop will fix your issue. Also, ES6 introduced the let keyword that scopes the variable in the way you are expecting:
for (let count = 0; count < 4; count++) { }
See this excellent introduction to javascript closures for more information.

Removing elements from a document in Javascript [duplicate]

This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 8 years ago.
Working on building this to-do list app to learn JS better.
I am able to insert text into box and have it add a new div element to the page with the relevant text residing within the div. The code that adds the div to the page automatically applies the class .newItem then adds an incrementing id i0, i1, i2, etc each time it's clicked. Everything works without an issue. Now, I have been fiddling around with the code to be able to click a single div element and have it remove itself from the page, but can't seem to get it to work.
var iDN = 0;
//Function that adds a new div with a custom incrementing id number
document.getElementById('add_button').onclick = function () {
var taskName = document.getElementById('add_task').value; // Store the value in the textbox into a variable
document.querySelector('.shit_to_do').innerHTML += '<div class = "newItem" id = "i' + iDN + '"' + 'onclick = removeEl()' + '>' + taskName + '</div>';
iDN += 1;
};
document.getElementById('testing').onclick = function () {
var parentNode = document.querySelector('.shit_to_do');
parentNode.removeChild(parentNode.children[0]);
}
function removeEl() {
for (i = 0; i < iDN; i++) {
if (document.getElementById('i' + i).onclick) {
document.getElementById('i' + i).display.style = 'none';
}
alert(i);
}
}
The for loop was really some random option I was trying to figure out how things were working onclick for each div, but didn't get to far with that one.
tl;dr:
I want to add click events to the new divs added onto the page in a single, universal function.
The value of document.getElementById('i' + i).onclick will be null if you've not set a handler to this attribute/property, otherwise it will be a function. null is always falsy, a function is always truthy.
To remove your element, you'll either have to look at this or e.target where e is the click event, and then call the DOM method node.removeChild(child).
The "quick and dirty" solution is to pass this into removeEl and remove it that way,
// ...
document.querySelector('.shit_to_do').innerHTML += '<div class="newItem" id="i' + iDN + '" onclick="removeEl(this)">' + taskName + '</div>';
// ...
function removeEl(elm) {
elm.parentNode.removeChild(elm);
}
I also removed the strange spacing between attribute names and values in your HTML
A perhaps "cleaner" solution is to create your nodes and attach listeners all by using DOM methods
function createDiv(index, taskname) {
var d = document.createElement('div');
d.setAttribute('id', 'i' + index);
d.textContent = taskname;
return d;
}
function removeElm() {
this.parentNode.removeChild(this);
}
var iDN = 0;
document.getElementById('add_button').addEventListener('click', function () {
var taskName = document.getElementById('add_task').value,
list = querySelector('.shit_to_do'),
div = createDiv(iDN, taskName);
div.addEventListener('click', removeElm);
list.appendChild(div);
iDN += 1;
});
This way means the browser does not re-parse any HTML as it not use element.innerHTML, which is a dangerous property may destroy references etc (along with usually being a bit slower)
Helpful links
node.addEventListener
document.createElement
node.appendChild

Adding div element to body or document in JavaScript

I am creating a light box in pure JavaScript. For that I am making an overlay. I want to add this overlay to body but I also want to keep the content on the page. My current code adds the overlay div but it also removes the current contents in body. How to add div element and keep contents on body?
var el = document.getElementById('element');
var body = document.getElementsByTagName('body');
el.innerHTML = '<p><a id="clickme" href="#">Click me</a></p>';
document.getElementById('clickme').onclick = function (e) {
e.preventDefault();
document.body.innerHTML = '<div style="position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;"></div>';
}
Using Javascript
var elemDiv = document.createElement('div');
elemDiv.style.cssText = 'position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;';
document.body.appendChild(elemDiv);
Using jQuery
$('body').append('<div style="position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;"></div>');
Try this out:-
http://jsfiddle.net/adiioo7/vmfbA/
Use
document.body.innerHTML += '<div style="position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;"></div>';
instead of
document.body.innerHTML = '<div style="position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;"></div>';
Edit:-
Ideally you should use body.appendChild method instead of changing the innerHTML
var elem = document.createElement('div');
elem.style.cssText = 'position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000';
document.body.appendChild(elem);
Instead of replacing everything with innerHTML try:
document.body.appendChild(myExtraNode);
improving the post of #Peter T, by gathering all solutions together at one place.
Element.insertAdjacentHTML()
function myFunction() {
window.document.body.insertAdjacentHTML( 'afterbegin', '<div id="myID" style="color:blue;"> With some data...</div>' );
}
function addElement(){
var elemDiv = document.createElement('div');
elemDiv.style.cssText = 'width:100%;height:10%;background:rgb(192,192,192);';
elemDiv.innerHTML = 'Added element with some data';
window.document.body.insertBefore(elemDiv, window.document.body.firstChild);
// document.body.appendChild(elemDiv); // appends last of that element
}
function addCSS() {
window.document.getElementsByTagName("style")[0].innerHTML += ".mycss {text-align:center}";
}
Using XPath find the position of the Element in the DOM Tree and insert the specified text at a specified position to an XPath_Element. try this code over browser console.
function insertHTML_ByXPath( xpath, position, newElement) {
var element = document.evaluate(xpath, window.document, null, 9, null ).singleNodeValue;
element.insertAdjacentHTML(position, newElement);
element.style='border:3px solid orange';
}
var xpath_DOMElement = '//*[#id="answer-33669996"]';
var childHTML = '<div id="Yash">Hi My name is <B>\"YASHWANTH\"</B></div>';
var position = 'beforeend';
insertHTML_ByXPath(xpath_DOMElement, position, childHTML);
The most underrated method is insertAdjacentElement.
You can literally add your HTML using one single line.
document.body.insertAdjacentElement('beforeend', html)
Read about it here - https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
The modern way is to use ParentNode.append(), like so:
let element = document.createElement('div');
element.style.cssText = 'position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;';
document.body.append(element);
You can make your div HTML code and set it directly into body(Or any element) with following code:
var divStr = '<div class="text-warning">Some html</div>';
document.getElementsByTagName('body')[0].innerHTML += divStr;
Try doing
document.body.innerHTML += '<div style="position:absolute;width:100%;height:100%;opacity:0.3;z-index:100;background:#000;"></div>'
The best and better way is to create an element and append it to the body tag.
Second way is to first get the innerHTML property of body and add code with it. For example:
var b = document.getElementsByTagName('body');
b.innerHTML = b.innerHTML + "Your code";
Here's a really quick trick:
Let's say you wanna add p tag inside div tag.
<div>
<p><script>document.write(<variablename>)</script></p>
</div>
And that's it.

(jquery) change nested same html tag to other bbcode tag

ok here is what i have:
<div id="mydiv">
<font color="green"><font size="3"><font face="helvetica">hello world</font></font></font>
</div>
I know the tags are strange, but that's what produced by the website.
So basically I want to change the font tag to bbcdoe tag, the jquery code I wrote:
$("#mydiv").find("font").text(function(){
var text = $(this).text();
var size = $(this).attr("size");
var color = $(this).attr("color");
var face = $(this).attr("face");;
if(size!=undefined){
return '[size="'+size+'"]'+text+'[/size]';
}
if(color!=undefined){
return '[color="'+color+'"]'+text+'[/color]';
}
if(face!=undefined){
return '[type="'+face+'"]'+text+'[/type]';
}
});
so what I got is only: [color="green"] hello world [/color]. always only the first tag. any idea?
ps: I tried each, replaceWith, html(), all the same result. only the first tag is change.
The reason it doesn't work is because when you call
$("#mydiv").find("font").text("New text")
For each font tag, starting from the first tag, it will replace the text within that tag.
Here is an example to show you what's going on.
Example | Code
$fonts = $("font","#mydiv");
console.log($fonts.text());
$fonts.text(function(){
return "New text";
});
console.log($fonts.text());
Here is an example of how you could do it instead
Example | Code
jQuery.fn.reverse = [].reverse;
var attributes= ["size", "color", "face"];
var text = $.trim($("#mydiv").text());
$("font","#mydiv").reverse().each(function(i, e) {
for (var i = 0; i < attributes.length; ++i){
var attr = $(e).attr(attributes[i]);
if( typeof attr != "undefined")
text = "["+attributes[i]+"="+attr+"]"+text+"[/"+attributes[i]+"]";
}
});
$("#mydiv").text(text);
A room full of sad, wailing kittens wishes that you'd get rid of those <font> tags, but you could probably make it work by explicitly working your way down through the nested tags.
It does what it does now because the outer call to .text() runs for the very first <font> tag, and it obliterates the other tags.
edit — to clarify, when you call
$('#mydiv').find('font')
jQuery will find 3 font tags. The library will therefore call the function you passed into .text() for each of those elements. However, the first call will have the effect of removing the other two <font> elements from the DOM. Even though the library proceeds to call your callback for those elements, there's no effect because they're not on the page anymore.
Here's what could work:
var $fonts = $('#mydiv').find('font');
var text = $fonts.text();
var attrs = {};
$fonts.each(function(_, font) {
var names = ["size", "color", "face"];
for (var i = 0; i < names.length; ++i)
if (font[names[i]]) attrs[names[i]] = font[names[i]];
});
var newText = "";
for (var name in attrs) {
if (attrs.hasOwnProperty(name))
newText += '[' + name + '=' + attrs[name] + ']';
}
newText += text;
for (var name in attrs) {
if (attrs.hasOwnProperty(name))
newText += '[/' + name + ']';
}
$('#mydiv').text(newText);
Note that I'm not really sure why you want to put the BBCode onto the page like that, but it seems to be the intention.
Seems to me your first line should be:
$("#mydiv").find("font").each(function(){

Javascript removeChild array

this looks simple enough but I just can't get it to work. I could add DOM elements but I just can't remove them when using an array.
<script language="javascript">
fields = 0;
count = 0;
function addInput() {
if (fields != 10) {
var htmlText = "<input type='search' value='' name='field[]' />";
var remButton = "<input type='button' value='del' onclick='remove()' />";
var newElement = document.createElement('div');
newElement.id = 'SomeID'+fields;
newElement.innerHTML = htmlText + remButton + newElement.id;
var fieldsArea = document.getElementById('text');
fieldsArea.appendChild(newElement);
fields += 1;
} else {
...
}
count++;
}
// NEED HELP FROM HERE PLEASE
// You don't need to Loop, just get the button's id and remove that entire 'SomeID'
function remove() {
fieldsArea = document.getElementById('text');
fieldsArea.removeChild(SomeID1); <-----------------------THIS WORKS!
fieldsArea.removeChild(SomeID+count); <------------------THIS JUST WOULDN'T
count--;
}
</script>
In the remove function, writing SomeID1 works and delete the first added element but when I try to use a 'count', I just can't delete my 'elements'.
Any help would be most appreciated.
Thank you!
You have to get a reference to the element first. Currently you are passing an undefined variable SomeID to the function.
E.g.:
var element = document.getElementById('SomeID' + fields);
// or starting by zero: var element = document.getElementById('SomeID0');
element.parentNode.removeChild(element);
If you want to remove the div for which the button was clicked, you have to pass a reference to the corresponding div to the remove function.
'<input type="button" value="del" onclick="remove(this.parentNode)" />';
this will refer to the button and as it is a child of the div, this.parentNode refers to that div.
You also have to change your function to accept the element that should be removed:
function remove(element) {
element.parentNode.removeChild(element);
count--;
}
You probably also have to update fields, but I'm not sure how your code is supposed to work.
If you want to remove all of them you have to loop:
for(;fields--;) {
var element = document.getElementById('SomeID' + fields);
element.parentNode.removeChild(element);
}
Also have a look at the documentation of removeChild.
The removeChild needs a node (DOM element) as parameter. In this case
fieldsArea.removeChild(SomeID+count);
you could for example pass the node this way
fieldsArea.removeChild(document.getElementById('SomeID'+count));

Categories

Resources