I just notice I couldn't do
var d1 = document.createElement('div');
var d2 = document.createElement('div');
var p = document.createElement('p');
d1.appendChild(p); // d1 has p now
d2.appendChild(p); // d2 has p now
// but where is p in d1 ?
Some would say it's logic, but well, when I first noticed that I thought how uncool it was.
Why isn't that possible ?
The DOM is a tree structure.
When you append an element, you change its parent.
A node, in the browser, is much more than just the text inside your P (that string could be shared, in fact). It also has a position, dimensions, a visibility, receives events that could have been fired in child elements, propagate events to its parent, and so on. Everything here depends on the position in the tree. Just like would many CSS selectors. It doesn't make a lot of sense to imagine it's the same element at two places, it's better to think about it as two nodes, with maybe some identical content.
If you want to have the same content at two places, you have to clone it.
jQuery's appendTo() method inserts "every element in the set of matched elements to the end of the target". Try this:
p.appendTo(".div-class1, .div-class2")
for AppendChild same element multiple times , we can use this way :
//main function
function appendChildMultiple(parent) {
//check function argument is an element
if (parent.nodeType !== undefined) {
const pTag = document.createElement("p");
pTag.innerText = "This is the appended element";
//finally append child to parent
parent.appendChild(pTag);
}
}
and :
// target the wrapper and create test elements
const wrapper = document.querySelector(".wrapper");
const d1 = document.createElement("div");
const d2 = document.createElement("div");
//append test elements to wrapper
wrapper.appendChild(d1);
wrapper.appendChild(d2);
//use appendChildMultiple function
appendChildMultiple(d1);
appendChildMultiple(d2);
//we appended "pTag" multiple times
if we use Functions , we can AppendChild same element multiple times whitout cloneNode
https://codepen.io/kiumad/pen/eYMNKYa
Related
I am trying to write some stuff into the HTML document multiple times. It's the same lines of code I want to write out. Basically copy itself.
var a = "asd";
let added = document.createElement("div");
let addedP = document.createElement("p")
addedP.innerText = a;
added.append(addedP);
document.body.append(added);
document.body.append(added);
I tried to do this, it wrote out "asd" on my page once, but I wanted it to do it twice.
An element can't appear in more than one place at once.
If you append an element that is already part of the document then it will be moved.
If you want multiple elements then you need to create them with, for example, createElement or cloneNode.
How about a custom function that returns a unique element? This should help with adding them in since they should all be considered unique:
var a = "asd";
let id = 0;
const newDiv = () => {
const x = document.createElement("div");
x.id = (id++).toString(); // sets its id and adds 1 to id simultaneously
return x;
}
let addedP = document.createElement("p") // same thing can be done for a unique p as for the div
addedP.innerText = a;
let added = addedP
added.append(newDiv()) // Can still append! (since a new element is returned)
document.body.append(added);
document.body.append(newDiv());
duplicate elements using element.cloneNode()
Elaborating on an earlier answer, the created element can be cloned to make a copy using:
element.cloneNode(true)
See: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
The true parameter value tells the .cloneNode method to deep clone, meaning it should include the subtree (specifying descendent elements and text nodes).
The following snippet clones the div in the question with and without the deep clone parameter set. When the deep clone parameter is not set, the element is empty.
One caveat to be aware of is that if the original element has an id attribute set, it should be re-set to hold a different value for all subsequent clones to avoid having elements with duplicate ids in the page.
var a = "asd";
const added = document.createElement("div");
const addedP = document.createElement("p")
addedP.innerText = a;
added.append(addedP);
// make a copy of added, including subtree;
copyAdded = added.cloneNode(true)
// clone again without the parameter set true;
anotherCopy = added.cloneNode()
document.body.append(added);
document.body.append(copyAdded);
document.body.append(anotherCopy);
div {
min-height: 20px;
border: 1px solid black;
background: yellow;
margin: 5px;
}
The first div was created in javascript. The second and third <i>cloned</i> from the first. Note the last paragraph lacks content because the <i>deep clone</i> parameter of <b>element.cloneNode()</b> was not set. The second paragraph used <b>element.cloneNode(true)</b> to specify a deep clone where the element and its subtree is duplicated.
I'm trying to get all classes from one element, then add them to another element created dynamically. I was originally stuck on how to do this, but as I was typing out this question, I worked out a solution. However, it seems a bit verbose. Is there a way to do this same thing more efficiently, i.e. with fewer lines of code?
let classes = this.nextElementSibling.classList; // get classes from target element
classes += ''; // convert classlist object to string
let class_array = classes.split(' '); // convert string to array
const my_div = document.createElement('div'); // create a new div
for(i=0; i<class_array.length; i++) { // loop through array and add classes to the div
my_div.classList.add(class_array[i]);
}
Thanks in advance.
The className will give you a space-separated string of class names an element has. Just use that.
const my_div = document.createElement('div');
my_div.className = this.nextElementSibling.className;
I'm trying to create a script that gets all the sections of documentation and creates a navigation with the id for a link and the H2 as the link text. I've tried several ways and the title is undefined. I've tried using a class, getting the first child node, and converting the nodelist to an array.
http://codepen.io/brooksroche/pen/XmpNaq?editors=101
if (document.getElementsByClassName("doc-section")) {
var sections = document.getElementsByClassName("doc-section"),
sidebar = document.getElementById('sidebarNav'),
navLinks = "";
for (var i = 0; i < sections.length; ++i) {
var current = sections[i],
anchorID = current.id,
title = current.childNodes[0].text,
navLink = '<li>' + title + '</li>',
navLinks = navLinks + navLink;
}
if (sidebar) {
sidebar.innerHTML = navLinks;
}
}
Use this instead (when collecting headers):
title = current.children[0].textContent
Demo. In other words, you should place children instead of childNodes, as with the latter both elements and text nodes are collected. Quoting the docs:
childNodes also includes e.g. text nodes and comments. To skip them,
use ParentNode.children instead.
In this particular case, whitespace between closing tag and opening tag was actually the first childNode of section element. You were able to get its text with .data property, but it was obviously of no use.
Actually, you might consider this as a safer alternative:
title = current.querySelector('.sectionTitle').textContent
... so that when the corresponding element's order is changed, you'll still be able to collect the text from it (if class is the same, of course).
I created a constructor that will handle a custom list control. I created a method in order to allow the user to add elements to the list, and I need to assign event handlers to the click events of the list elements (divs).
A simplified version of the code is here. The list elements are created using the innerHTML property and a string template upon which I substitute specific parts. Later I get the element by it's id and assign it a function in closure:
function prueba(){
var plantilla = '<div id="«id»">«texto»</div>';
var f = function(nombre){
return function(){console.log('mi nombre es ' + nombre)};
};
this.agregar = function(id, texto){
var tmp = plantilla.replace('«id»', id);
tmp = tmp.replace('«texto»', texto);
document.body.innerHTML += tmp;
document.getElementById(id).onclick = f(id);
};
};
The problem is that, apparently, the event handler is unasigned to previous created divs, so is only retained by the last one, as it can be tested with the following code:
var p = new prueba;
p.agregar('i1', 'texto1');
console.log(document.getElementById('i1').onclick.toString());//shows the function code
p.agregar('i2', 'texto2');
console.log(document.getElementById('i2').onclick.toString());//shows the function code
console.log(document.getElementById('i1').onclick.toString());//returns 'null' error
p.agregar('i3', 'texto3');
console.log(document.getElementById('i3').onclick.toString());//shows the function code
console.log(document.getElementById('i2').onclick.toString());//returns 'null' error
This happens in Iceweasel as well as in Chromium. It does NOT happen when I add 'onclick = f(«id»)' in the template (which I cannot do here because of the assigned function scope), and neither happens if I use document.createElement. What am I doing wrong?
You destroy elements previously created when you do this:
document.body.innerHTML += tmp;
Instead use insertAdjacentHMTL() if you want to append using HTML markup.
document.body.insertAdjacentHTML("beforeend", tmp);
Now instead of going through this destructive process...
serialize the existing DOM nodes to HTML
concatenate the new HTML fragment to the serialized nodes
destroy the old nodes
recreate the nodes with the new nodes
...it simply creates the new content and places it before the close of the body element.
Basically, remove element.innerHTML += ... from your coding practices. It's never necessary, it's inefficient and it causes problems like what you've described.
FYI, the .insertAdjacentHTML() method receives 4 different string possibilities as the first argument. Each one designates a position relative to the element on which you're calling it.
The strings are...
"beforebegin"
"afterbegin"
"beforeend"
"afterend"
The labels are pretty self-explanatory. They position the new content before the current element, inside the current element at the beginning, inside the current element at the end, or after the current element, respectively.
Your full code will look like this, which I shortened a bit too since the tmp really isn't needed here:
function prueba(){
var plantilla = '<div id="«id»">«texto»</div>';
var f = function(nombre){
return function(){console.log('mi nombre es ' + nombre)};
};
this.agregar = function(id, texto){
document.body.insertAdjacentHTML("beforeend",
plantilla.replace('«id»', id)
.replace('«texto»', texto));
document.getElementById(id).onclick = f(id);
};
};
I am wanting to get an attribute of the parent element that my cursor is inside of.
I have been totally unsuccessful for about two hours now, so I'm posting to see if anybody has any suggestions. This is the function I have but don't know why parentID only returns undefined:
function getAttrOfParent() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
var parentID = $(parentElement).attr('id');
alert(parentID);
}
This works fine to get the text of the parent element...
function getTextOfParent() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
var parentText = $(parentElement).text();
alert(parentText);
}
...and this works fine to get the title of a specified element.
function getAttrOfElement() {
var parentID = $('#1').attr('id');
alert(parentID);
}
Here's my jsFiddle, you have to click inside of the text area for the first two functions to work.
parentElement = newRange.commonAncestorContainer isn't giving you the parent element, it is giving you the parent node.
This is fairly obvious if you console.log(parentElement) to see what is being selected.
If you select text entirely inside one of your spans (which have ids) then you get the text node (which doesn't have an id).
If you select more text, you get the paragraph element node (which doesn't have an id).
You might want to try something like var parentElement = $(newRange.commonAncestorContainer).parents('span');