How to programmatically append a newly created div to an existing one? - javascript

I have some trouble with appending a new div to an existing parent that I just created. After creation of the parent I check it's existence. But when I want to append the child to it after selecting the parent via it's id I get an error. What do I do wrong?
var uiDiv = document.createElement("div");
uiDiv.id = "f_jsonuiDiv";
uiDiv.innerHTML = "jsonUI controls";
console.log("uiDiv");
console.dir(uiDiv); //shows uiDiv object
//select container div
const parentId = "f_jsonuiDiv"; //which is the id of the newly created container div
console.log("parentId: ",parentId);
var parElement = document.getElementById(parentId);
console.log("parElement: ");
console.dir(parElement); //says: null !
//create directly
//const newDiv = parElement.createElement("div"); //throws error as parElement does not exist ......
//create first, then append
const newDiv = document.createElement("div");
newDiv.innerHTML = "NEW DIV";
//parElement.appendChild(newDiv); //throws error as parElement does not exist ......
uiDiv.appendChild(newDiv); //does not throw an error ```

Seems like you need to add uiDiv to body (or any other parent) first, in order to get it with getElementById
document.body.appendChild(uiDiv);
// This should be valid now
const parElement = document.getElementById(parentId);

You need to put the script after the body let the DOM be created .
Or warp your code with
window.addEventListener('DOMContentLoaded', (event) => { //put your code here });
it will run after the page is loaded

A would advise to use insertAdjacentElement and insertAdjacentHTML. It makes your life easier.
// insertAdjacentElement returns the newly created element
const uiDiv = document.body.insertAdjacentElement(`beforeend`,
Object.assign(document.createElement("div"), {
id: "f_jsonuiDiv",
innerHTML: "jsonUI controls" })
);
// so now you can inject it wit some html
uiDiv.insertAdjacentHTML(`beforeend`,`<div>HI, I am the NEW DIV in town</div>`);
#f_jsonuiDiv div {
color: red;
padding: 2px 1em;
border: 1px solid #AAA;
max-width: 400px;
text-align: center;
}

Related

How to style/target a node that is created within a function, from outside of the function

I want to be able to style (or change) div(s) created within a function. However, this "change" must not be made in the very same function. Given that variables created in functions is in the local scope of that function, how do I reach the created div(s) for later styling (or other changes for that matter) from outside? I have tried targeting the node with querySelector but Console returns Uncaught TypeError since it cannot find the variable.
Example code in JS:
let container = document.querySelector('.container');
let divClass = document.querySelector('.div-class');
divClass.style = "backgroundColor: green";
container.addEventListener('click', () => {
let createdDiv = document.createElement('div');
container.appendChild(createdDiv);
createdDiv.classList.add('div-class');
})
The problem with your code is that you query before the element has even been created.
let container = document.querySelector('.container');
container.addEventListener('click', () => {
let createdDiv = document.createElement('div');
container.appendChild(createdDiv);
createdDiv.classList.add('div-class');
})
let someButton= document.querySelector('.someButton');
someButton.addEventListener('click', () => {
let divClass = document.querySelector('.div-class');
divClass.style.backgroundColor = "green";
})
.container,.someButton{
border:1px solid black;
background:grey;
margin-bottom:4em;
}
.div-class{
height:40px;
width:40px;
border:1px solid red;
}
<div class="container">
container
</div>
<div class="someButton">
someButton
</div>
In this snippet, the access to the created div is put in another click event. If you click .someButton before .container it will create an error, but if you click after it will work since the div will be created.
You can make a variable in global scope, and assign it the div which you create later.
const container = document.querySelector('.container');
// assign sample element to avoid error if new_div is used before assigning.
let newDiv = document.createElement('div');
container.addEventListener('click', () => {
let newDiv = document.createElement('div');
container.appendChild(newDiv);
newDiv.classList.add('div-class');
})
newDiv.style = "backgroundColor: green";

Only the last element I added using innerHTML keeps its event handlers, why?

I am trying to make a script that injects interactable object information in a list of the markup page. Whenever I try to add an onclick event on a div, it works fine, however whenever I try to add more within a for loop, it does not work the way I intended.
I took a look of what is going on using breakpoints in the webpage debugger, and I see that the problem is that it seems to delete the event on the previous div before adding to the next div. In the end, the only event remaining is the last div after the loop exits.
I want to keep these events on all my divs, not just the last one... what seems to be the problem here?
var objects = ['Tom', 'Sauna', 'Traum'];
for (var i = 0; i < objects.length; i++){
document.getElementById('list').innerHTML += "<div class='item' id='"+ i +"'>" + objects[i] + "</div>";
document.getElementById(i).addEventListener("mouseup", function() {
Select(this);
});
}
function Select(char) {
console.log(char);
}
div.item {
border: 1px solid black;
padding: 4px;
margin: 4px;
}
<div id="list"></div>
When you change innerHTML browser reconstructs the element's contents, throwing away all event handlers attached. Use DOM methods instead:
for (let i = 0; i < objects.length; i++){
var block = document.createElement('div');
block.setAttribute('id', i);
document.getElementById('list').appendChild( block );
block.addEventListener("mouseup", function() {
Select(this);
});
}
UPD: alternatively use a insertAdjacentHTML method instead of redefining innerHTML:
document.getElementById('list').insertAdjacentHTML(
'beforeend', "<div id='"+ i +"'>" + i + "</div>");
The reason is the way you are appending. innerHtml += effectively overwrites the existing content in the list. So, any elements that you added and bound are simply gone, and new items are added each time.
There are a couple ways to make this work.
First instead of assigning an innerHtml you can append elements.
const items = ['taco', 'apple', 'pork'];
const list = document.getElementById("list");
for (const item of items) {
const el = document.createElement("div");
el.addEventListener('click', (e) => console.log(`clicked ${item}`));
el.innerText = item;
list.appendChild(el);
}
<div id="list"></div>
Since we are appending an explicit element and not overwriting content, this will work.
A better approach would be to use delegation. We assign a single event handler onto the list and listen for any clicks. We then figure out what specific element was clicked.
const items = ['taco', 'apple', 'pork'];
const list = document.getElementById("list");
const add = document.getElementById("add");
list.addEventListener('click', (e) => {
const parent = e.target.closest("[data-item]");
if (parent != null) {
console.log(`clicked on ${parent.dataset['item']}`);
}
});
for (const item of items) {
list.innerHTML += `<div data-item="${item}">${item}</div>`;
}
add.addEventListener('click', () => {
const item = `item ${Date.now()}`;
list.innerHTML += `<div data-item="${item}">${item}</div>`;
})
<div id="list"></div>
<button id="add">add</button>
The magic here is we assign a single event handler on the parent, and use closest to figure out what item was clicked. I'm using innerHTML here for simplicity but it should be avoided for security reasons.
A good pattern to use when appropriate is event delegation. It allows following the Don't Repeat Yourself principle, making code maintenance considerably easier and potentially making scripts run significantly faster. And in your case, it avoids the pitfalls of an element being responsible for modifying its own content.
For example:
const container = document.getElementById('container');
container.addEventListener("click", toggleColor); // Events bubble up to ancestors
function toggleColor(event) { // Listeners automatically can access triggering events
const clickedThing = event.target; // Event object has useful properties
if(clickedThing.classList.contains("click-me")){ // Ensures this click interests us
clickedThing.classList.toggle("blue");
}
}
.click-me{ margin: 1em 1.5em; padding 1em 1.5em; }
.blue{ color: blue; }
<div id="container">
<div id="firstDiv" class="click-me">First Div</div>
<div id="firstDiv" class="click-me">Second Div</div>
</div>

How to get the direct children of a specified tag in xml using JQuery

Please check the below code. I need to check only the children that are defined inside the parent tag.
If any unexpected child appears in that parent I need to report that as error.
This is my XML File:
<places>
<Tirunelveli>XXXX</Tirunelveli>//allowed child of places
<Tiruchendur></Tiruchendur>//allowed child of places
<Alwar></Alwar>//allowed child of places
<sweet></sweet>//not allowed child of places and i have to report this tag as error
</places>
I need to check that the <places> parent only has child tags that are allowed. Otherwise I need to add it as an error.
var rd = new FileReader();
rd.onload = function(e){
var xmlDoc = $.parseXML(this.result);
var $xml = $(xmlDoc);
//check allowed child of front tag
check_allowed_direct_child("places", "Tirunelveli,Tiruchendur,Alwar", "RULE_002", "Fail");
//Function to check the x tag have only the alowed child y
function check_allowed_direct_child(x,y,rule,error_type)
{
var child_array = y.split(',');
var child_count=child_array.length;
var ischild=""
var xmlchild="";
$xml.children(x).each(function()
{
ischild="no";
xmlchild=this.value;
for(i=0;i<count;i++)
{
if(child_array[i]==xmlchild)
{
ischild="yes";
}
}
if(ischild=="no")
{
//count the total error and warnings
check_total_error_warning(error_type);
$("#validation_report").append('<tr class="err"><td><a href="Asset\Rules\Rule.html\#'+rule+'">'+rule+'</td><td><span class="highlight"><'+xmlchild+'></span> is not allowed inside the <span class="highlight"><'+x+'></span> element</td><td class="'+classname+'">'+error_type+'</td></tr>');
}
});
}
};rd.readAsText(this.files[i]);
But the children() code is not working. What am I doing wrong?
The way you select the parent node needs correction: use find to locate it, then use children on that result (without argument) to get all the children.
If the parent node is the root of the XML, then find will not find it, but with addBack you can also include the root node in the match.
I would also use camelCase for your function names, and use jQuery functions to build dynamic HTML.
Here is how that could look:
var $xml = $("<aa><bb></bb><cc></cc><dd></dd><ee></ee></aa>");
var className = "someClass"
//check allowed child of front tag
checkChildAllowed($xml, "aa", ["bb","cc","dd"], "RULE_002", "Fail");
//Function to check the x tag have only the allowed child y
function checkChildAllowed($xml, parent, allowedChildren, rule, errorType) {
// Make sure to first locate the parent correctly:
var $parent = $xml.find(parent).addBack(parent);
// Uppercase tag names as jQuery uses that HTML convention
allowedChildren = allowedChildren.map(childName => childName.toUpperCase());
$parent.children().each(function () {
if (allowedChildren.includes(this.nodeName)) return; // tag is OK
// Include this if that function is defined:
//check_total_error_warning(error_type);
$("#validation_report").append( // Use jQuery functions
$("<tr>").addClass("err").append(
$("<td>").append(
$("<a>").attr("href", "Asset\Rules\Rule.html\#"+rule).text(rule)
),
$("<td>").append(
$("<span>").addClass("highlight")
.text("<"+this.nodeName.toLowerCase()+">"),
" is not allowed inside the ",
$("<span>").addClass("highlight").text("<"+parent+">"),
" element"
),
$("<td>").addClass(className).text(errorType)
)
);
});
}
table { border-collapse: collapse }
td { border: 1px solid; padding: 5px }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="validation_report"></table>
What you can do, is that you loop through your child elements of your parent element of your choice. Your parent element in this case is "places".
You can the use the javascript function .tagName to get the element type of the child. You can then construct a condition around that to check for the allowed / disallowed child(ren).
Example:
var rd = new FileReader();
var xmlDoc = $.parseXML(this.result);
var $xml = $(xmlDoc);
var classname='errorClass';
function check_allowed_direct_child(x="places",
y="Tirunelveli,Tiruchendur,Alwar",
rule="RULE_002",
error_type="Fail") {
var notAllowedChild='sweet';
var child_array=y.split(",");
for(i=0; child_array.length > i; i++) {
if(child_array[i] != notAllowedChild) {
alert(child_array[i]+' is an allowed child');
}
}
$(''+x).children().each(function () {
var elements=this.tagName.toLowerCase();
if(elements == notAllowedChild) {
alert(elements+' is not an allowed child');
$("#validation_report").append('<tr class="err"><td><a href="#">'+rule+'</td><td><span class="highlight"><'+elements+'></span> is not allowed inside the <span class="highlight"><'+x+'></span> element</td><td class="'+classname+'">'+error_type+'</td></tr>');
}
});
}
table, th, td {
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<places>
<Tirunelveli>XXXX</Tirunelveli>
<Tiruchendur></Tiruchendur>
<Alwar></Alwar>
<sweet></sweet>
</places>
<table id="validation_report"></table>
<br />
<button onclick="check_allowed_direct_child();">Test</button>

Add css before appending child

So I have a div (with the id of "thecolor2") that I want to append to an unordered list, but before I append it, I want to set its background color to a variable which has the value of a hex code. However, for some reason, it doesn't take in the color.
Here is the CSS:
#thecolor2{
height: 50px;
width: 50px;
border-radius: 100px;
border: 1px solid yellow;
position: relative;
bottom: 635px;
}
Her is the HTML:
<ul id = "allposts"></ul>
And here is the JS:
var thestream = document.getElementById('allposts');
var oneofpost = document.createElement('li');
var thecolor2 = document.createElement('div');
thecolor2.id = "thecolor2";
$("#thecolor2").css("background-color", color);
thestream.appendChild(oneofpost);
thestream.appendChild(thecolor2);
You cant use a jQuery ID selector to match a node which hasn't been added to the document tree. You can simply use plain DOM to set its inline CSS style like this:
thecolor2.style.backgroundColor = color
As described by Carlo in another answer, you cannot use the jQuery selector to select elements that haven't been added. You can however, turn a created DOM element into a jQuery object by doing:
var thecolor2 = $(document.createElement('div'));
However, if you're going to be using jQuery then I suggest writing everything in jQuery, otherwise stick with using pure JavaScript for everything.
jQuery
var thestream = $('#allposts');
var oneofpost = $('<li></li>');
var thecolor2 = $('<div></div>');
thecolor2.prop('id', "thecolor2")
.css({
backgroundColor: color
}).appendTo(oneofpost);
thestream.append(oneofpost);
See jsFiddle
JavaScript
var thestream = document.getElementById('allposts');
var oneofpost = document.createElement('li');
var thecolor2 = document.createElement('div');
thecolor2.id = "thecolor2";
thecolor2.style.backgroundColor = color;
oneofpost.appendChild(thecolor2);
thestream.appendChild(oneofpost);
See jsFiddle
Also I'm assuming you're trying to append a list item to the ul, so I corrected the code you had there with appendChild.

Wrapping a set of DOM elements using JavaScript

I have a series of p tags on my page and I want to wrap them all into a container, e.g.
<p>foo</p>
<p>bar</p>
<p>baz</p>
I want to wrap all the above tags into a container as follows:
<div>
<p>foo</p>
<p>bar</p>
<p>baz</p>
</div>
How to wrap a NodeList in an element using vanilla JavaScript?
Posted below are a pure JavaScript version of jQuery's wrap and wrapAll methods. I can't guarantee they work exactly as they do in jQuery, but they do in fact work very similarly and should be able to accomplish the same tasks. They work with either a single HTMLElement or an array of them. I haven't tested to confirm, but they should both work in all modern browsers (and older ones to a certain extent).
Unlike the selected answer, these methods maintain the correct HTML structure by using insertBefore as well as appendChild.
wrap:
// Wrap an HTMLElement around each element in an HTMLElement array.
HTMLElement.prototype.wrap = function(elms) {
// Convert `elms` to an array, if necessary.
if (!elms.length) elms = [elms];
// Loops backwards to prevent having to clone the wrapper on the
// first element (see `child` below).
for (var i = elms.length - 1; i >= 0; i--) {
var child = (i > 0) ? this.cloneNode(true) : this;
var el = elms[i];
// Cache the current parent and sibling.
var parent = el.parentNode;
var sibling = el.nextSibling;
// Wrap the element (is automatically removed from its current
// parent).
child.appendChild(el);
// If the element had a sibling, insert the wrapper before
// the sibling to maintain the HTML structure; otherwise, just
// append it to the parent.
if (sibling) {
parent.insertBefore(child, sibling);
} else {
parent.appendChild(child);
}
}
};
See a working demo on jsFiddle.
wrapAll:
// Wrap an HTMLElement around another HTMLElement or an array of them.
HTMLElement.prototype.wrapAll = function(elms) {
var el = elms.length ? elms[0] : elms;
// Cache the current parent and sibling of the first element.
var parent = el.parentNode;
var sibling = el.nextSibling;
// Wrap the first element (is automatically removed from its
// current parent).
this.appendChild(el);
// Wrap all other elements (if applicable). Each element is
// automatically removed from its current parent and from the elms
// array.
while (elms.length) {
this.appendChild(elms[0]);
}
// If the first element had a sibling, insert the wrapper before the
// sibling to maintain the HTML structure; otherwise, just append it
// to the parent.
if (sibling) {
parent.insertBefore(this, sibling);
} else {
parent.appendChild(this);
}
};
See a working demo on jsFiddle.
You can do like this:
// create the container div
var dv = document.createElement('div');
// get all divs
var divs = document.getElementsByTagName('div');
// get the body element
var body = document.getElementsByTagName('body')[0];
// apply class to container div
dv.setAttribute('class', 'container');
// find out all those divs having class C
for(var i = 0; i < divs.length; i++)
{
if (divs[i].getAttribute('class') === 'C')
{
// put the divs having class C inside container div
dv.appendChild(divs[i]);
}
}
// finally append the container div to body
body.appendChild(dv);
I arrived at this wrapAll function by starting with Kevin's answer and fixing the problems presented below as well as those mentioned in the comments below his answer.
His function attempts to append the wrapper to the next sibling of the first node in the passed nodeList. That will be problematic if that node is also in the nodeList. To see this in action, remove all the text and other elements from between the first and second <li> in his wrapAll demo.
Contrary to the claim, his function won't work if multiple nodes are passed in an array rather than a nodeList because of the looping technique used.
These are fixed below:
// Wrap wrapper around nodes
// Just pass a collection of nodes, and a wrapper element
function wrapAll(nodes, wrapper) {
// Cache the current parent and previous sibling of the first node.
var parent = nodes[0].parentNode;
var previousSibling = nodes[0].previousSibling;
// Place each node in wrapper.
// - If nodes is an array, we must increment the index we grab from
// after each loop.
// - If nodes is a NodeList, each node is automatically removed from
// the NodeList when it is removed from its parent with appendChild.
for (var i = 0; nodes.length - i; wrapper.firstChild === nodes[0] && i++) {
wrapper.appendChild(nodes[i]);
}
// Place the wrapper just after the cached previousSibling,
// or if that is null, just before the first child.
var nextSibling = previousSibling ? previousSibling.nextSibling : parent.firstChild;
parent.insertBefore(wrapper, nextSibling);
return wrapper;
}
See the Demo and GitHub Gist.
Here's my javascript version of wrap(). Shorter but you have to create the element before calling the function.
HTMLElement.prototype.wrap = function(wrapper){
this.parentNode.insertBefore(wrapper, this);
wrapper.appendChild(this);
}
function wrapDiv(){
var wrapper = document.createElement('div'); // create the wrapper
wrapper.style.background = "#0cf"; // add style if you want
var element = document.getElementById('elementID'); // get element to wrap
element.wrap(wrapper);
}
div {
border: 2px solid #f00;
margin-bottom: 10px;
}
<ul id="elementID">
<li>Chair</li>
<li>Sofa</li>
</ul>
<button onclick="wrapDiv()">Wrap the list</button>
If you're target browsers support it, the document.querySelectorAll uses CSS selectors:
var targets = document.querySelectorAll('.c'),
head = document.querySelectorAll('body')[0],
cont = document.createElement('div');
cont.className = "container";
for (var x=0, y=targets.length; x<y; x++){
con.appendChild(targets[x]);
}
head.appendChild(cont);
Taking #Rixius 's answer a step further, you could turn it into a forEach loop with an arrow function
let parent = document.querySelector('div');
let children = parent.querySelectorAll('*');
let wrapper = document.createElement('section');
wrapper.className = "wrapper";
children.forEach((child) => {
wrapper.appendChild(child);
});
parent.appendChild(wrapper);
* { margin: 0; padding: 0; box-sizing: border-box; font-family: roboto; }
body { padding: 5vw; }
span,i,b { display: block; }
div { border: 1px solid lime; margin: 1rem; }
section { border: 1px solid red; margin: 1rem; }
<div>
<span>span</span>
<i>italic</i>
<b>bold</b>
</div>

Categories