Related
I have many of the below 'k-top' div elements, with the same inner div structure, except different unique text in two places, in 'k-in' and in my checkbox id.
<div class="k-top">
<span class="k-icon k-i-expand"></span><-------------- trigger click on this if below text is found
<span class="k-checkbox-wrapper" role="presentation">
<input type="checkbox" tabindex="-1" id="unique TEXT99" class="k-checkbox">
<span class="k-checkbox-label checkbox-span"></span>
</span>
<span class="k-in">unique TEXT99</span></div><- if this text is found in k-in trigger click on elem above
I want to iterate through all my span.k-ins until I find the innerText to match contains of 'unique' for instance, then once unique is found, I want to .click(); on it's sibling element '.k-i-expand' as seen in the mark-up above. I do not want to trigger a .click(); on all .k-i-expand just the specific one that has same parent as where my 'unique text' is found.
Thus far I have tried .closest, I have also tried sibling.parent.. both return null or undefined.. Note, I am not using jQuery.
The below works successfully to click all .k-i-expand - but I need to .click() only the one where k-in innerText contains 'unique'. Ideally I'd use starts with, or contains, but I'd specify the whole word if needed i.e. unique TEXT99
let exp = document.querySelectorAll('.k-i-expand');
let i;
for (i = 0; i < exp.length; ++i) {
exp[i].click();
};
More previous attempts can be seen here: how to run a .click on elems parent sibling selector?
I created a recursive function which checks all it's Siblings until it finds one with the specified innerHTML. If it does not find one, it does nothing:
function checkSibling(node) {
if (node.innerHTML == "unique TEXT99") {
return true;
} else if (node.nextSibling) {
return checkSibling(node.nextSibling);
} else {
return false;
}
}
async function clickOnNode() {
let exp = document.querySelectorAll(".k-i-expand");
for await (const node of exp) {
const hasText = await checkSibling(node);
if (hasText) {
console.log("Result: ", hasText);
node.click();
}
}
}
clickOnNode();
I also created a codepen with the code for you to play around. I guess the innerHTML check could be improved via a Regex.
Have you tried iterating over the .k-top elements and looking into each one to find your .k-in?
const expandItemsContaining = (text) => {
// Let's get all the .k-top divs
const kTops = document.querySelectorAll('.k-top');
// And peek into each and every one of them
kTops.forEach(kTop => {
// First we check whether there is a .k-in containing your text
const kIn = kTop.querySelector('.k-in');
const shouldClick = kIn && kIn.innerText && kIn.innerText.indexOf(text) !== -1;
// And if there is one we find the .k-i-expand and click it
if (shouldClick) {
const kExpand = kTop.querySelector('.k-i-expand');
if (kExpand) {
kExpand.click();
}
}
})
}
Given a simple structure like this:
<td><span>Text1</span></td>
<td><span>Text2</span></td>
<td><span>Text3</span></td>
<td><span><a href='#'>Link</a>Text4</span></td>
I am trying to extract all Text1-4 with Javascript, w/out any child nodes.
Loop for the cols
...
x = rows[i].getElementsByTagName("TD")[n].getElementsByTagName('span')[0];
...
Output for each x
Test1
Test2
Test3
<a href='#'>Link</a>Text4
Is there a simple way to "ignore" the html tags of an element?
Edit
I tried this
if(x.hasChildNodes()){
x = rows[i].getElementsByTagName("TD")[n].getElementsByTagName('span')[0].getElementsByTagName('a')[0];
}
but that gives me Link obviously
use .innerText which will get text and all nested text for you, and replace div with td for your example, I just added it to show result
const tags = document.querySelectorAll('div > span');
tags.forEach(tag => {
const nodes = tag.childNodes;
nodes.forEach(node => {
if(node.nodeType === node.TEXT_NODE) {
console.log(node.nodeValue);
}
});
});
<div><span>Text1</span></div>
<div><span>Text2</span></div>
<div><span>Text3</span></div>
<div><span><a href='#'>Link</a>Text4</span></div>
I'm trying to get the ID of an element by class name like this
var prod_id2 = document.getElementsByClassName('select-selected')[1].id
document.getElementById('hidden-input-2').value = prod_id2;
This works fine, but my issue is that if there's only one element with that class it breaks the functionality, so I need some sort of if statement to only define this var if there is a second div with that class.
Any ideas?
Try this:
const elements = document.querySelectorAll('.test');
if (elements[1]) {
elements[1].innerText = 'Hithere';
}
<div class="test">hi</div>
<div class="test">hi</div>
<div class="test">hi</div>
document.querySelectorAll('.test'); selects all elements with the class test and returns a nodelist.
Then we can access the second element via of the nodelist with elements[1].
Here is how to check for the second element.
You can also set another fallback , different to null:
document.addEventListener("DOMContentLoaded", function(event) {
var selectedElements = document.querySelectorAll('.selected-selected'),
prod_id2 = selectedElements[1] || null;
alert(prod_id2)
});
<div id="test" class="selected-selected"></div>
You can also check that value then:
if (prod_id2) { // do some other stuff with the set value }
It breaks the functionality I think because you are grabbing the 2nd element specifically. You can do:
const prod_id2 = document.querySelectorAll('.select-selected');
and loop over the elements and grab the ID
prod_id2.forEach((prod, index) => {
if(index === 2) {
document.getElementById('hidden-input-2').value = prod.id;
}
})
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>`;
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");