Is there a way to select sibling nodes? - javascript

For some performance reasons, I am trying to find a way to select only sibling nodes of the selected node.
For example,
<div id="outer">
<div id="inner1"></div>
<div id="inner2"></div>
<div id="inner3"></div>
<div id="inner4"></div>
</div>
If I selected inner1 node, is there a way for me to access its siblings, inner2-4 nodes?

Well... sure... just access the parent and then the children.
node.parentNode.childNodes[]
or... using jQuery:
$('#innerId').siblings()
Edit: Cletus as always is inspiring. I dug further. This is how jQuery gets siblings essentially:
function getChildren(n, skipMe){
var r = [];
for ( ; n; n = n.nextSibling )
if ( n.nodeType == 1 && n != skipMe)
r.push( n );
return r;
};
function getSiblings(n) {
return getChildren(n.parentNode.firstChild, n);
}

var sibling = node.nextSibling;
This will return the sibling immediately after it, or null no more siblings are available. Likewise, you can use previousSibling.
[Edit] On second thought, this will not give the next div tag, but the whitespace after the node. Better seems to be
var sibling = node.nextElementSibling;
There also exists a previousElementSibling.

Quick:
var siblings = n => [...n.parentElement.children].filter(c=>c!=n)
https://codepen.io/anon/pen/LLoyrP?editors=1011
Get the parent's children as an array, filter out this element.
Edit:
And to filter out text nodes (Thanks pmrotule):
var siblings = n => [...n.parentElement.children].filter(c=>c.nodeType == 1 && c!=n)

From 2017:
straightforward answer: element.nextElementSibling for get the right element sibling. also you have element.previousElementSibling for previous one
from here is pretty simple to got all next sibiling
var n = element, ret = [];
while (n = n.nextElementSibling){
ret.push(n)
}
return ret;

have you checked the "Sibling" method in jQuery?
sibling: function( n, elem ) {
var r = [];
for ( ; n; n = n.nextSibling ) {
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
the n.nodeType == 1 check if the element is a html node and n!== exclude the current element.
I think you can use the same function, all that code seems to be vanilla javascript.

There are a few ways to do it.
Either one of the following should do the trick.
// METHOD A (ARRAY.FILTER, STRING.INDEXOF)
var siblings = function(node, children) {
siblingList = children.filter(function(val) {
return [node].indexOf(val) != -1;
});
return siblingList;
}
// METHOD B (FOR LOOP, IF STATEMENT, ARRAY.PUSH)
var siblings = function(node, children) {
var siblingList = [];
for (var n = children.length - 1; n >= 0; n--) {
if (children[n] != node) {
siblingList.push(children[n]);
}
}
return siblingList;
}
// METHOD C (STRING.INDEXOF, ARRAY.SPLICE)
var siblings = function(node, children) {
siblingList = children;
index = siblingList.indexOf(node);
if(index != -1) {
siblingList.splice(index, 1);
}
return siblingList;
}
FYI: The jQuery code-base is a great resource for observing Grade A Javascript.
Here is an excellent tool that reveals the jQuery code-base in a very streamlined way.
http://james.padolsey.com/jquery/

The following function will return an array containing all the siblings of the given element.
const getSiblings = node => [...node.parentNode.children].filter(c => c !== node)
// get "c" element siblings (excluding itself)
const siblingsToC = getSiblings(document.querySelector('.c'))
console.log( siblingsToC )
<ul>
<li class='a'>a</li>
<li class='b'>b</li>
<li class='c'>c</li>
<li class='d'>d</li>
<li class='e'>e</li>
</ul>
Just pass the selected element into the getSiblings() function as it's only parameter.

Here's how you could get previous, next and all siblings (both sides):
function prevSiblings(target) {
var siblings = [], n = target;
while(n = n.previousElementSibling) siblings.push(n);
return siblings;
}
function nextSiblings(target) {
var siblings = [], n = target;
while(n = n.nextElementSibling) siblings.push(n);
return siblings;
}
function siblings(target) {
var prev = prevSiblings(target) || [],
next = nexSiblings(target) || [];
return prev.concat(next);
}

Use document.querySelectorAll() and Loops and iteration
function sibblingOf(children,targetChild){
var children = document.querySelectorAll(children);
for(var i=0; i< children.length; i++){
children[i].addEventListener("click", function(){
for(var y=0; y<children.length;y++){children[y].classList.remove("target")}
this.classList.add("target")
}, false)
}
}
sibblingOf("#outer >div","#inner2");
#outer >div:not(.target){color:red}
<div id="outer">
<div id="inner1">Div 1 </div>
<div id="inner2">Div 2 </div>
<div id="inner3">Div 3 </div>
<div id="inner4">Div 4 </div>
</div>

jQuery
$el.siblings();
Native - latest, Edge13+
[...el.parentNode.children].filter((child) =>
child !== el
);
Native (alternative) - latest, Edge13+
Array.from(el.parentNode.children).filter((child) =>
child !== el
);
Native - IE10+
Array.prototype.filter.call(el.parentNode.children, (child) =>
child !== el
);

var childNodeArray = document.getElementById('somethingOtherThanid').childNodes;

1) Add selected class to target element 2) Find all children of parent element excluding target element 3) Remove class from target element
<div id = "outer">
<div class="item" id="inner1">Div 1 </div>
<div class="item" id="inner2">Div 2 </div>
<div class="item" id="inner3">Div 3 </div>
<div class="item" id="inner4">Div 4 </div>
</div>
function getSiblings(target) {
target.classList.add('selected');
let siblings = document.querySelecttorAll('#outer .item:not(.currentlySelected)')
target.classList.remove('selected');
return siblings
}

You can access the following sibling nodes, with the currentNode.nextSibiling property.
This is how you can do in the event delegation way, which is a dynamic way to add event listeners
document.addEventListener('click', (event) => {
if (event.target.matches("#inner1")) {
console.log(event.targert.nextSibling); //inner2 div
console.log(event.targert.nextSibling.nextSibling); //inner3 div
/* The more of the property you keep appending the further it goes to
the next sibling */
}
})

My use case was different. I had to select a few spans which didn't have any id/classes (nor their parents), just an entry point (#target). Once you have that, run a querySelectorAll on its parent with the appropriate selector, using :scope as you can't simply use > div or > span or > .foo.
Note that this approach ALSO selects the target element, if it matches the selector. In the below example, I'd have to use :scope > span:not(#target) to avoid selecting the entry point.
const spanSiblings = document.getElementById("target").parentNode.querySelectorAll(":scope > span");
console.log([...spanSiblings].map(e => e.innerText));
<div>
<span>One</span>
<span id="target">Target</span>
<div>A</div>
<span>Two</span>
<div>B</div>
<div>Hey</div>
</div>

BEST SOLUTION:
This is the best solution according my opinion:
let inner2 = event.target.parentNode.querySelector(`#inner2`)
/*Or if you have already stored the inner1 node to a variable called: inner1*/
let inner2 = inner1.parentNode.querySelector(`#inner2`)
At the first line the event.target will be the inner1 node, if we click on that. The parentNode will be the "outer" node, and on the partent node we start a search ( .querySelector(#inner2) ) to select the inner2 node.
OTHER SOLUTIONS:
I list other possible options, but they are not that flexible, since at them the sequence of the nodes are matter, which makes the code fragile, if we later add another node to the parent the whole code will break, what we want to avoid:
2)
This selects the first child (this index starts from 1, and NOT from 0)
node.parentNode.childNodes[1]
3) Assume that you have already selected inner1Node to a variable, the next sibling you can get:
let inner2Node = inner1Node.nextElementSibling;
4) The previous sibling you can get:
let inner1NodeAGAIN = inner2Node.previousElementSibling;

x1 = document.getElementById('outer')[0]
.getElementsByTagName('ul')[1]
.getElementsByTagName('li')[2];
x1.setAttribute("id", "buyOnlineLocationFix");

Related

How to clone an element?

I have made a script to clone elements in js but this only selects the first element. Here is the code :
function clone(sel) {
var rVxyz = document.querySelector(sel);
var rVabc = rVxyz.cloneNode(true);
document.body.appendChild(rVabc) ||
document.documentElement.appendChild(rVabc);
};
Yes I know I have used querySelector but I was not able to use it with querySelectorAll. I wrote something like this :
function clone(sel, num) {
var rVxyz = document.querySelectorAll(sel)[num];
var rVabc = rVxyz.cloneNode(true);
document.body.appendChild(rVabc) ||
document.documentElement.appendChild(rVabc);
};
This only works when num = 0, otherwise it doesn't.
Thanks in advance.
This will depend on what exactly you pass to your function as sel. If you are passing the id of an element, like this: #someID, there can only be 0 elements or 1 element in rVxyz as ids must be unique. So you can't clone that element, unless you include some code to change the ID, in which case it's not really a clone anyway.
Of course, if you are selecting by class, by passing something like .someClass to sel, then there can be any number of elements, from 0 to n.
I think it honestly makes more sense to clone all matching elements, rather than trying to pass an index to your function. So the clone() would use forEach instead.
function clone(sel)
{
var rVxyz = document.querySelectorAll(sel);
rVxyz.forEach(function(element) {
let rVabc = element.cloneNode(true);
document.body.appendChild(rVabc) ||
document.documentElement.appendChild(rVabc);
});
};
clone("#biz");
clone(".boi");
clone(".boi:nth-child(2)");
The HTML for test:
<div id="biz"><p>1 Unique thing</p></div>
<div class="boi"><p>2 hi</p></div>
<div class="boi"><p>hello there</p></div>
The output is:
1 Unique thing
2 hi
3 hello there
2 hi
3 hello there
2 hi
The first element is not cloned because IDs must be unique. Also, notice in the second call to clone(), I used the nth-child selector, so only the second matching element within each parent element is cloned. The nice thing about this is that you can even select every second, third, fourth, etc. element if you want.
As long as the elements are within the same parent container element, this will work.
Here's some more info about nth-child.
But your original approach would work as well if you actually want to clone the nth match. E.g. the 2nd match. You just need to check that you aren't going beyond the end of the NodeList rVxyz.
function clone(sel, num)
{
var rVxyz = document.querySelectorAll(sel);
var limit = rVxyz.length;
if (num < limit)
{
var rVabc = rVxyz[num].cloneNode(true);
document.body.appendChild(rVabc) ||
document.documentElement.appendChild(rVabc);
}
else
{
console.log(limit);//Do nothing or output error message here.
}
};
clone(".boi", 1);
This clones the second matching element only, as long as there are 2 or more elements found.
But your code works... You just could add a few lines, to get less errors.
function clone(sel, num) {
num = parseInt(num);
var id = !!sel.match('#');
var elem = (id) ? document.querySelector(sel) : document.querySelectorAll(sel)[num];
if (elem) {
var clone = elem.cloneNode(true);
clone.classList.add('demo'); // just for demo (remove);
if (id || elem.id) {
clone.id = elem.id + '-bubuClone';
console.log( `New clone id="${clone.id}"` );
}
document.body.appendChild(clone) || document.documentElement.appendChild(clone);
} else {
console.log(
'Didn\'t found element to clone: ("' + sel + '")' + ( !id ? '[' + num + ']' : '' )
);
}
}
clone('.test', 2);
clone('.test', 5);
clone('.test', 9);
clone('.test', 15);
clone('#bubu', 15);
clone('#check');
.test, .test-id {
display: inline-block;
border: 2px solid orange;
padding: 5px;
margin: 5px;
}
.demo {
border-color: red;
}
<div class="test-id" id="bubu">bubu</div>
<div class="test">0</div>
<div class="test">1</div>
<div class="test" id="test007">2</div>
<div class="test">3</div>
<div class="test">4</div>
<div class="test">5</div>
<div class="test">6</div>
<div class="test">7</div>
<div class="test">8</div>
<div class="test">9</div>
<br>
Or, add the third parameter to function, which will define the search type ( selector / selectorAll )

Get childNode attributes with JavaScript

I'm trying to get the attributes of an element's children ... something like this:
<div id="mydivs">
<div name="mydiv1" name="format1">
</div>
<div name="mydiv2" format="format2">
</div>
</div>
var parent = document.getElementById('mydivs');
var children = parent.childNodes;
for (let child of children) {
console.log(child)
console.log(child.attributes)
}
child.atrribute returns undefined
JsFiddle: https://jsfiddle.net/8tdac5fn/
Edit: Another option would be to use children instead of childNodes
var parent = document.getElementById('mydivs');
var children = parent.children;
parent.childNodes will include text nodes and that is why you are getting undefined, so you can first check if nodeType is 1 which is Node.ELEMENT_NODE.
Then you could use spread syntax in array to create array of attribute nodes so you can use forEach loop.
var parent = document.getElementById('mydivs');
var children = parent.childNodes;
for (let child of children) {
if (child.nodeType === 1) {
[...child.attributes].forEach(({name,value}) => {
console.log(`${name} - ${value}`);
})
}
}
<div id="mydivs">
<div name="mydiv1" name="format1"></div>
<div name="mydiv2" format="format2"></div>
</div>

How to get the previous text node that is displayed before a given one?

I´m trying to implement my own caret and when it´s at the start of a text and go to the left it should go to the previous text node in the DOM. My problem is that this is not always just a previous sibling which I could find easiely. It could be that it is in at the bottom of the tree of a sibling of the parent. This should clarify my problem:
function getPreviousTextElement(node) {
var prev = $('.caret').closest(':text');
prev.css('color', 'green');
}
#carot{
color:red;
}
<div>
1 <div>
2
<div>3</div>
<div>4</div>
<div>5
<div>6</div>
<div>
7
<div>8</div>
<div>9</div>
</div>
</div>
</div>
</div>
<div>
<span id="carot">|</span>10
</div>
So when the caret is at "10" and you press left it should go to "9" but how do I get this element? Are there JS or jQuery functions I´m missing? jQuery closest(), prevAll() or parents() doesn´t seem to do the job.
https://jsfiddle.net/jqb816a1/5/
I tried a few different solutions to this. Initially I figured that basic upwards traversal from the DOM element would work, but unfortunately since you specified in your question that you wanted it to also go through the siblings of an unknown number of parent elements as well, I couldn't quite get that to jive.
I ended up creating an object that contained a Set Object to hold onto all those text nodes, and a getPrev method that will return the previous text node when given the element containing a text node on the page. In this case your span tag with the id of carot
The Setup
function findTextNodes(node) {
let text_node_set = new Set();
traversal(node);
function traversal(node) {
check_for_text(node);
let ele = node.nextSibling;
while (ele) {
check_for_text(ele);
ele = ele.nextSibling;
}
}
function check_for_text(ele) {
if (ele.childNodes) {
for (let child of ele.childNodes) {
if (child.nodeType == 3 && new RegExp(/\S/g).test(child.textContent)) {
text_node_set.add(child);
} else {
traversal(child);
}
}
}
}
return {
getPrev: function(ele_node) {
let text_node;
if (ele_node.childNodes) {
for (let child of ele_node.childNodes) {
if (child.nodeType == 3 && new RegExp(/\S/g).test(child.textContent)) {
text_node = child;
}
}
}
if (this.text_node_set.has(text_node)) {
let prev, previousNode;
this.text_node_set.forEach(function(node) {
if (node === text_node) {
if (prev) previousNode = prev;
}
prev = node;
})
return previousNode;
}
},
text_node_set
}
}
Using the return Object:
let text_nodes = findTextNodes(document.body);
// object: text_nodes
// methods: getPrev
// properties: text_node_set
let caret = document.getElementById('carot');
//print the previous node to the console
console.log(text_nodes.getPrev(carot));
//or turn its text green
let prevNode = text_nodes.getPrev(carot);
//(we need to grab the parent node of the text for styling)
prevNode.parentNode.style.color = "lightGreen";

Selecting relative child tags jquery

I am currently new to JavaScript (I am using jQuery) and I was wondering how I could select all the children Something tags from the following HTML
<Something attribute="first">
<div class="container">
<Something attribute="second" />
<Something attribute="third">
<Something attribute="fourth" />
</Something>
</div>
</Something>
<Something attribute="fifth">
<Something attribute="sixth" />
</Something>
I had code like so to select both the child Something tags
$("Something[attribute='first']").children("Something").each({});
but this does not work because of the div in between. How can I bypass all tags that are not Something and select only those elements that are one level deep if you remove all the tags that are not Something? So if I want to query the children of the Something tag with attribute first I would get second and third (not fourth or sixth). Similarly if I query fifth I would get sixth
NOTE Sorry for being unclear about this but I only want the Something tags one level after the Something tag whose children I am trying to find. So for example in the above HTML I do not want the query to return the Something tag with attribute fourth. So in essence if you strip out the tags in between every other Something tag I want the tags that are only one level deep from the one in question.
NOTE There can be other tags in between the Something tags, not just the one div. For example the above can be
<Something attribute="first">
<div class="container">
<div class="container">
<Something attribute="second" />
<Something attribute="third">
<Something attribute="fourth" />
</Something>
</div>
</div>
</Something>
<Something attribute="fifth">
<div>
<div>
<Something attribute="sixth" />
</div>
</div>
</Something>
and the same selection criteria would apply. So the results would be
first -> [second, third]
third -> [fourth]
[fifth] -> [sixth]
A recursive solution in pseudocode for what I want would be
function get_immediate_children_for(element, array_of_children) {
$(element).children().each(function() {
if (this.tagName == "SOMETHING") {
array_of_children.push(this);
}
else {
get_immediate_children_for(this, array_of_children);
}
});
}
and you would call this as so
var array_of_children = get_immediate_children_for($("Something[attribute='first']"));
JSFiddle: https://jsfiddle.net/qdhnfdcx/
var elem = document.getElementsByTagName("something");
function getChildren(name) {
var list = []
var len = name.length;
for (var i = 0; i < len; i++) {
if(name[i].parentElement.className != name[i].className) {
for (var j = 0; j < len; j++) {
return list.push(name[i].children[j]));
}
}
}
}
getChildren(elem);
Do the following:
var allChildren = document.getElementsByTagName("Something");
Then you can simply select the corresponding index:
allChildren[0];
allChildren[1];
Or you could loop and edit all of them:
for (var i = 0, len = allChildren.length; i < len; i++) {
allChildren[i].style.display = "none";
}
If you want certain somethings not to appear then do:
for (var i = 0, len = allChildren.length; i < len; i++) {
if(allChildren[i].getAttribute("attribute") != "fourth") {
allChildren[i].style.display = "none";
}
}
If you only want the something tags one level after the something tags:
for (var i = 0, len = allChildren.length; i < len; i++) {
if(allChildren[i].className == allChildren[i].parentElement.className) {
allChildren[i].style.display = "none";.
}
}
EDIT (due to edited question)
If you want a single level after the child of a specific element, then use something like this:
$("Something[attribute='first']").children("div").children("Something").each({});
Instead of .children() use .find()
(from jquery docs)
The .children() method differs from .find() in that .children() only travels a single level down the DOM tree while .find() can traverse down multiple levels to select descendant elements (grandchildren, etc.) as well.
$("Something").each(function() {
var childSomething = $(this).find("Something");
$.each(childSomething,function() {
//do something
});
});
You can use jquery find function to achieve that. Like this:
$("Something[attribute='first']").find("div > Something").each({});
Thanks for the answers! I do not believe that jQuery or Javascript either have a native way to do this but a simple recursive solution to this would be
function get_immediate_children_for(element) {
var array_of_children = [];
get_immediate_children_for_helper(element, array_of_children);
console.log(array_of_children);
return array_of_children;
}
function get_immediate_children_for_helper(element, array_of_children) {
$(element).children().each(function() {
console.log(this.tagName);
if (this.tagName == "ACTIVITY") {
array_of_children.push(this);
console.log(array_of_children);
}
else {
get_immediate_children_for_helper(this, array_of_children);
}
});
}
So that now get_immediate_children_for($("Activity[path='']")) would return the elements one level deep ignoring all other tags
var smth = document.getElementsByTagName('Something')[0];
var chindren = smth.getElementsByTagName('Something');
for(var i = 0, el;el = children[i];++i){
//do what you want
}
Edit 1 (final)
I answered to the first edition of the question.
Possible answer is to convert / read current document as XML and manage accordingly. Or parse inner/outerHTML to a node tree. There are a lot of pitfalls on both ways (including browser comatibility, at least) but it is the only approach to the problem.

Append multiple items in JavaScript

I have the following function and I am trying to figure out a better way to append multiple items using appendChild().
When the user clicks on Add, each item should look like this:
<li>
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
</li>
and I have this function to add these elements:
function addNewItem(listElement, itemInput) {
var listItem = document.createElement("li");
var listItemCheckbox = document.createElement("input");
var listItemLabel = document.createElement("label");
var editableInput = document.createElement("input");
var editButton = document.createElement("button");
var deleteButton = document.createElement("button");
// define types
listItemCheckbox.type = "checkbox";
editableInput.type = "text";
// define content and class for buttons
editButton.innerText = "Edit";
editButton.className = "edit";
deleteButton.innerText = "Delete";
deleteButton.className = "delete";
listItemLabel.innerText = itemText.value;
// appendChild() - append these items to the li
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
if (itemText.value.length > 0) {
itemText.value = "";
inputFocus(itemText);
}
}
But you can notice that I am repeating three times the appendChild() for listItem. Is it possible to add multiple items to the appendChild() ?
You can do it with DocumentFragment.
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
listElement.appendChild(documentFragment);
DocumentFragments allow developers to place child elements onto an
arbitrary node-like parent, allowing for node-like interactions
without a true root node. Doing so allows developers to produce
structure without doing so within the visible DOM
You can use the append method in JavaScript.
This is similar to jQuery's append method but it doesnot support IE and Edge.
You can change this code
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
to
listElement.append(listItem,listItemCheckbox,listItemLabel,editButton,deleteButton);
Personally, I don't see why you would do this.
But if you really need to replace all the appendChild() with one statement, you can assign the outerHTML of the created elements to the innerHTML of the li element.
You just need to replace the following:
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
With the following:
listItem.innerHTML+= listItemCheckbox.outerHTML + listItemLabel.outerHTML + editButton.outerHTML + deleteButton.outerHTML;
listElement.appendChild(listItem);
Explanation:
The outerHTML attribute of the element DOM interface gets the serialized HTML fragment describing the element including its descendants. So assigning the outerHTML of the created elements to the innerHTML of the li element is similar to appending them to it.
Merging the answers by #Atrahasis and #Slavik:
if (Node.prototype.appendChildren === undefined) {
Node.prototype.appendChildren = function() {
let children = [...arguments];
if (
children.length == 1 &&
Object.prototype.toString.call(children[0]) === "[object Array]"
) {
children = children[0];
}
const documentFragment = document.createDocumentFragment();
children.forEach(c => documentFragment.appendChild(c));
this.appendChild(documentFragment);
};
}
This accepts children as multiple arguments, or as a single array argument:
foo.appendChildren(bar1, bar2, bar3);
bar.appendChildren([bar1, bar2, bar3]);
Update – June 2020
Most all current browsers support append and the "spread operator" now.
The calls above can be re-written as:
foo.append(bar1, bar2, bar3);
bar.append(...[bar1, bar2, bar3]);
Let's try this:
let parentNode = document.createElement('div');
parentNode.append(...[
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div')
]);
console.log(parentNode);
You need to append several children ? Just make it plural with appendChildren !
First things first :
HTMLLIElement.prototype.appendChildren = function () {
for ( var i = 0 ; i < arguments.length ; i++ )
this.appendChild( arguments[ i ] );
};
Then for any list element :
listElement.appendChildren( a, b, c, ... );
//check :
listElement.childNodes;//a, b, c, ...
Works with every element that has the appendChild method of course ! Like HTMLDivElement.
You can use createContextualFragment, it return a documentFragment created from a string.
It is perfect if you have to build and append more than one Nodes to an existing Element all together, because you can add it all without the cons of innerHTML
https://developer.mozilla.org/en-US/docs/Web/API/Range/createContextualFragment
// ...
var listItem = document.createElement("li");
var documentFragment = document.createRange().createContextualFragment(`
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`)
listItem.appendChild(documentFragment)
// ...
You could just group the elements into a single innerHTML group like this:
let node = document.createElement('li');
node.innerHTML = '<input type="checkbox"><label>Content typed by the user</label> <input type="text"><button class="edit">Edit</button><button class="delete">Delete</button>';
document.getElementById('orderedList').appendChild(node);
then appendChild() is only used once.
It's possible to write your own function if you use the built in arguments object
function appendMultipleNodes(){
var args = [].slice.call(arguments);
for (var x = 1; x < args.length; x++){
args[0].appendChild(args[x])
}
return args[0]
}
Then you would call the function as such:
appendMultipleNodes(parent, nodeOne, nodeTwo, nodeThree)
Why isn't anybody mentioning the element.append() function ?!
you can simply use it to append multiple items respectively as so:
listItem.append(listItemCheckbox, listItemLabel, editButton, deleteButton);
This is a quick fix
document.querySelector("#parentid .parenClass").insertAdjacentHTML('afterend', yourChildElement.outerHTML);
Guys I really recommend you to use this one.
[listItemCheckbox, listItemLabel, editButton, deleteButton]
.forEach((item) => listItem.appendChild(item));
Since you can't append multiple children at once. I think this one looks better.
Also here's a helper function that uses the fragment technique as introduced in the #Slavik's answer and merges it with DOMParser API:
function createHtmlFromString(stringHtml) {
const parser = new DOMParser();
const htmlFragment = document.createDocumentFragment();
const children = parser.parseFromString(stringHtml, "text/html").body
.children;
htmlFragment.replaceChildren(...children);
return htmlFragment;
}
Now to append multiple children with this, you can make the code much more readable and brief, e.g.:
const htmlFragment = createHtmlFromString(`<div class="info">
<span></span>
<h2></h2>
<p></p>
<button></button>
</div>
<div class="cover">
<img />
</div>
`);
Here's also a working example of these used in action: example link.
Note1: You could add text content in the above tags too and it works, but if it's data from user (or fetched from API), you'd better not trust it for better security. Instead, first make the fragment using the above function and then do something like this:
htmlFragment.querySelector(".info > span").textContent = game.name;
Note2: Don't use innerHTML to insert HTML, it is unsecure.
Great way to dynamically add elements to a webpage. This function takes 3 arguments, 1 is optional. The wrapper will wrap the parent element and it's elements inside another element. Useful when creating tables dynamically.
function append(parent, child, wrapper="") {
if (typeof child == 'object' && child.length > 1) {
child.forEach(c => {
parent.appendChild(c);
});
} else {
parent.appendChild(child);
}
if (typeof wrapper == 'object') {
wrapper.appendChild(parent);
}
}
I would like to add that if you want to add some variability to your html, you can also add variables like this:
let node = document.createElement('div');
node.classList.add("some-class");
node.innerHTML = `<div class="list">
<div class="title">${myObject.title}</div>
<div class="subtitle">${myObject.subtitle}
</div>`;

Categories