querySelectorAll root elements without using :scope - javascript

Let's assume we have this HTML structure:
<div id='test-div'>
<div class='random-class-1'>
<div class='useless-element-1'>
</div>
<div class='useless-element-2'>
</div>
<p>Just a paragraph, and useless</p>
</div>
<div class='random-class-2'>
<div class='useless-element-3'>
</div>
<div class='useless-element-4'>
</div>
<div class='useless-element-5'>
</div>
</div>
</div>
I need to select all children "DIV elements" (not grandchildren) inside the first DIV (in this example with id='test-div'), not from document but from element (div) itself.
So, I don't want to use the "query" below because I already have selected the element DIV [object HTMLDivElement]:
// I don't want to use this query
var children = document.querySelectorAll("div > div");
Just an example to achieve this (https://jsfiddle.net/t4gxt65k/):
// I already have selected DIV element
var el = document.getElementById("test-div")
// OR var el = document.querySelectorAll("#test-div");
var children = el.querySelectorAll(":scope > div");
But because of browser incompatibility I don't want to use ":scope"
The real question is:
How can I get the children (only DIV elements) of [object HTMLDivElement] using pure JavaScript?

As an option, you could set a temporary unique attribute for your scope element, and then use querySelectorAll() for its parent with the attribute selector prepended to what you would place after the :scope selector:
// The `scope` variable stores a reference to the scope element.
var attrName = '_scope' + Date.now();
scope.setAttribute(attrName, '');
var children = scope.parentNode.querySelector('[' + attrName + '] > DIV');
I’m not sure about how fast it is though.
Fwiw, specifically for getting child elements, there is the children DOM property:
var children = scope.children;

To get direct children of an element use a combination of parentNode.children or parentNode.childNodes, and Array.prototype.reduce like this:
var children = Array.prototype.reduce.call(el.children, function(acc, e) {
if(e.tagName == "DIV")
acc.push(e);
return acc;
}, []);

Similar to #Ibrahims answer in selecting children but less complicated since it does not address prototype and reduce. Instead it uses Array.from, Array.filter and Element.matches. Element.matches makes it more versatile since it uses a normal selector string (as you would in querySelectorAll).
Array.from(rootElement.children)
.filter(elm => elm.matches('div'))
const rootElement = document.getElementById('test-div')
Array.from(rootElement.children)
.filter(elm=>elm.matches('div'))
.forEach(elm=>elm.classList.add('matched'))
div {
padding: 1rem;
box-shadow: 0 0 0 1px gray inset;
}
.matched {
box-shadow: 0 0 0 1px red inset;
}
<div id='test-div'>
<div class='random-class-1'>
<div class='useless-element-1'></div>
<div class='useless-element-2'></div>
<p>Just a paragraph, and useless</p>
</div>
<div class='random-class-2'>
<div class='useless-element-3'></div>
<div class='useless-element-4'></div>
<div class='useless-element-5'></div>
</div>
</div>

Related

Javascript: Delete a div that contains (3) in a row <BR>'s in it

<div>
<br>
<br>
<br>
</div>
I have an HTML page that has the above div element in it. How can I scan the page an look for a DIV that contains specific items? In this example I know I have three <br> in a row and that's it. The DIV element does not have a class or an ID, and I would like to delete it.
You can target elements in a row by using the sibling selector, +. You can make sure the target elements are a direct child of the div by using the > selector.
You can find the div that contains the three br elements like so:
document.querySelector('div > br + br + br').parentNode;
To remove this element from the DOM use the remove method.
const elToDelete = document.querySelector('div > br + br + br')?.parentNode;
elToDelete?.remove();
Edit:
Added optional chaining syntax to the answer to show how to prevent undefined errors from being thrown.
You can use the :has pseudo-class to select the div directly
var elem = document.querySelectorAll("div:has(>br+br+br)");
console.log([...elem])
elem.forEach(x=>x.remove())
<div id=a> a
<br/>
<br/>
<br/>
</div>
<div id=b> b
<br/>
<br/>
</div>
<div id=c> c
<br/>
<br/>
<not-br/>
<br/>
</div>
You can try this:
[].slice.call(document.getElementsByTagName("div")).forEach(function(div){
var count = 0;
if(div.children.length === 3)
[].slice.call(div.children).forEach(function(br, i){
if(br.nodeName === "BR")
count++;
});
if(count > 2)
div.parentElement.removeChild(div);
});
You can just hide it with CSS
div:has(>br+br+br) {
display: none;
}
<div>1</div>
<div><br/><br/><br/></div>
<div>3</div>
Best solution that will work cross-browser is to tackle this programmatically. Note that :has() pseudo class doesn't work on Firefox, IE or Opera (see https://caniuse.com/css-has).
Here's a concise functional Javascript approach:
[].filter.call(document.querySelectorAll('div'), el => el.querySelectorAll('br').length === 3)

Can I clone, edit and append a div?

I'm trying to find out if it's possible to clone an HTML div with JS, edit it and append it again as a new element. So my source is, for example, this code here:
<div class="wrapper">
<div class="element">
<input id="test--1" value="ABC"/>
</div>
</div>
After copying this element, I need to find a way to change the attribute id of the new cloned input, clear the input value and paste it again in the wrapper so that it looks like this at the end:
<div class="wrapper">
<div class="element">
<input id="test--1" value="ABC"/>
</div>
<div class="element">
<input id="test--2" value=""/>
</div>
</div>
Does that make sense to you? If yes, how can I get this done? Or is it better to assign the content to a variable to append it? I'm looking for the best way here and maybe my idea is a solution too.
You can use pure JavaScript to do this by just cloning the .element div using the cloneNode() method, assign new id and value to the clone div and finally append it back to the document using the insertBefore() method like this:
let x = document.querySelector(".element");
let y = x.cloneNode(true);
y.children[0].id = "test--2";
y.children[0].defaultValue = "";
x.parentNode.insertBefore(y, x.nextSibling);
<div class="wrapper">
<div class="element">
<input id="test--1" value="ABC"/>
</div>
</div>
JSFiddle with the above code: https://jsfiddle.net/AndrewL64/jvc7reza/18/
Based on this answer you could do like:
$('#cloneBtn').on('click', function() {
// get the last input having ID starting with test--
var $inp = $('[id^="test--"]:last'); // Or use :first if you need
// Get parent element
var $div = $inp.closest('.element');
// Create clone
var $div_clone = $div.clone();
// Retrieve number from ID and increment it
var num = parseInt($inp.prop("id").match(/\d+/g), 10) + 1;
// Generate new number and assign to input
$div_clone.find('[id^="test--"]').prop({id: 'test--' + num, value: ''});
// Insert cloned element
$div.after($div_clone); // Or use .before() if you need
});
.element {
padding: 10px;
outline: 2px solid #0bf;
}
<button id="cloneBtn">CLICK TO CLONE</button>
<div class="wrapper">
<div class="element">
<input id="test--1" value="ABC" />
</div>
</div>
Once done inspect the input elements to see the new IDs
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
Scrambled elements, retrieve highest ID, increment, clone, append.
If your numbered IDs are scrambled, we first need a way to retrieve the highest ID number. Here's an implementation in pure JavaScript:
function cloneElement () {
const inpAll = document.querySelectorAll('[id^="test--"]');
if (!inpAll.length) return; // do nothing if no elements to clone
const maxID = Math.max.apply(Math, [...inpAll].map(el => +el.id.match(/\d+$/g)[0]));
const incID = maxID + 1;
const element = document.querySelector('.element'); // Get one for cloning
const eleClone = element.cloneNode(true);
const inpClone = eleClone.querySelector('[id^="test--"]');
inpClone.id = 'test--'+ incID;
inpClone.value = incID; // just for test. Use "" instead
document.querySelector('.wrapper').prepend(eleClone);
}
document.querySelector('#cloneBtn').addEventListener('click', cloneElement);
.element {
padding: 10px;
outline: 2px solid #0bf;
}
<button id="cloneBtn">CLICK TO CLONE</button>
<div class="wrapper">
<div class="element">
<input id="test--1" value="1" />
</div>
<div class="element">
<input id="test--23" value="23" />
</div>
<div class="element">
<input id="test--7" value="7" />
</div>
</div>
Once done inspect the input elements to see the new IDs
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
I don't know what data you know, why you want to do such a thing but it can be done :-)
One way is like that:
const elemToCopy = document.getElementById("test--1").parentNode; // I assume you know id
const copiedElem = elemToCopy.cloneNode(true);
const newInput = copiedElem.querySelector("#test--1");
newInput.id = "test--2";
newInput.value = "";
elemToCopy.parentNode.append(copiedElem);
Let me know in a comment if something is not clear :-)
Yes, use jQuery's .clone().
Here is an example that might be relevant to your situation:
let newElement = $('.element').clone();
newElement.find('input').attr('id', 'test--2').val('');
$('.wrapper').append(newElement);
Explanation
In the first line, we created a new cloned element by using jQuery clone().
Then, we found it's child input, changed it's ID and reset the val().
Finally, we found the .wrapper element and appended the new cloned element to it.

Clear style on every DOM element

I'm trying to find a smart way of clearing previously changed style property on every DOM element.
I know there is a way to clear styles like this
(margin is just an example attribute):
element_name.style.margin = "";
Clearing many elements with many styles attributes this way will take ages, so I hope there is some "all_at_once" solution.
This will clear the style of all tags:
const tags = document.querySelectorAll("*");
for (let i of tags) {
i.style = '';
}
<div style="background-color: beige">
<span style="color: red">Hello</span>
<p style="world">World</p>
</div>
You could also use i.removeAttribute('style'); to remove the style attribute.
You can get all the elements by using
document.querySelectorAll("*")
You will get an object with all the elements with numbered keys. Object.keys will give you an array of all the keys and with forEach you can iterate and apply the style or any manipulation to the elements.
var elem = document.querySelectorAll("*");
Object.keys(elem).forEach((e)=>elem[e].style.margin = "32px")
<div>scscs</div>
This can also be used
document.getElementsByTagName("*");
var elem = document.getElementsByTagName("*");
Object.keys(elem).forEach((e)=>elem[e].style.margin = "")
<div>scscs</div>
The document.getElementsByTagName("*"); shouldn't be used due to performance issues as mentioned by #ScottMarcus in comments
You have to remove inline styles and disable stylesheets:
function clearStyles() {
// remove inline styles:
document.querySelectorAll('*').forEach(e => e.style = null);
// disable stylesheets:
Object.values(document.styleSheets).forEach(v => v.disabled = true);
}
h1 {
color: pink
}
<h1 style="background:navy">My color was set in stylesheet<br>and background was set inline</h1>
<button onclick="clearStyles()">Clear styles</button>
.cssText
.cssText treats the whole value of .style as a string:
document.documentElement.querySelectorAll('*').forEach(tag => tag.style.cssText = '');
This expression will collect all tags within the <html> tag and overwrite all .style properties.
Demo
document.documentElement.querySelectorAll('*').forEach(tag => tag.style.cssText = '');
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>
<div style='color:red'>RED</div>

Select a div if it contains all specified elements

I'm attempting to select the .item div which contains both tagOne and tagTwo span elements.
My div structure is as follows:
<div id="items">
<div id="block1" class="item">
<span class="tagOne tag">One</span>
<span class="tagTwo tag">Two</span>
</div>
<div id="block2" class="item">
<span class="tagOne tag">Java</span>
</div>
</div>
Using the following jQuery I'm able to locate the tags (with their parent div's) separately.
var blocks = $('#items .item');
blocks.filter('.item').find('[class*="tagOne"]').parent();
blocks.filter('.item').find('[class*="tagTwo"]').parent();
However, once I try to combine them to narrow it down to the one div that contains them both, I get no results and I can't seem to work out why!
blocks.filter('.item').find('[class*="tagOne"][class*="tagTwo"]');
My understanding is that the comma syntax will create an OR expression, and without creates an AND expression. I'm after the AND expression as I only want to return the div that contains all the criteria, or nothing at all.
Note: I'm doing it this way because I'm creating a toggle-filter based on the tags, and the criteria (i.e. tagOne, tagTwo) is a concatenation of the tags selected by the user (not shown) so it is preferable to try to do it in one operation.
EDIT: Moved duplicate id's to class names instead to make it valid and tweaked JavaScript code accordingly.
First of all, the ID should be unique. Now, the markup contains two elements with ID tagOne which is invalid markup.
You can use class instead of ID.
Select any of the element from the two(.tagOne or .tagTwo in this case)
Use siblings() to select the sibling element having the other class
Use closest() to select closest ancestor matching the selector.
The step #1, #2 and #3 above will select only those .item elements having both .tagOne and .tagTwo as descendent.
Code:
$('.tagOne') // Select one of the element
.siblings('.tagTwo') // Get second element if it is sibling
.closest('.item') // Get the closest ancestor
$('.tagOne') // Select one of the element
.siblings('.tagTwo') // Get second element if it is sibling
.closest('.item') // Get the closest ancestor
.addClass('selected'); // For Demo purpose
.item {
color: red;
}
div.selected {
color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">
<div id="block1" class="item">
<span class="tagOne tag">One</span>
<span class="tagTwo tag">Two</span>
</div>
<div id="block2" class="item">
<span class="tagOne tag">Java</span>
</div>
<div id="block3" class="item">
<span class="tagTwo tag">I Love JavaScript</span>
</div>
</div>
You can also use filter as follow.
Iterate over all the .item elements using filter()
Use context selector to check if the current .item has descendent .tagOne and .tagTwo.
Use length property on the jQuery object to get the number of elements selected by the selector.
Code:
$('.item').filter(function() {
return $('.tagOne', this).length && $('.tagTwo', this).length;
})
// Fiddle: https://jsfiddle.net/tusharj/8tuu1wxs/1/
// Iterate over all elements having item class
$('.item').filter(function() {
return $('.tagOne', this).length && $('.tagTwo', this).length;
}).addClass('selected');
.item {
color: red;
}
.selected {
color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">
<div id="block1" class="item">
<span class="tagOne tag">One</span>
<span class="tagTwo tag">Two</span>
</div>
<div id="block2" class="item">
<span class="tagOne tag">Java</span>
</div>
<div id="block3" class="item">
<span class="tagTwo tag">I Love JavaScript</span>
</div>
</div>
If the sequence/order of the elements is fixed, CSS general sibling selector ~ or adjacent sibling selector + can be used.
$('.tag1 ~ .tag2').closest('.item')
OR
$('.tag1 + .tag2').closest('.item')
// Fiddle: https://jsfiddle.net/tusharj/amdoLfou/1/
$('.tag1 ~ .tag2') // + can also be used instead of ~
.closest('.item') // Get closest ancestor
.css('color', 'blue'); // For Demo purpose
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="items">
<div id="block1" class="item">
<span class="tag1 tag">One</span>
<span class="tag2 tag">Two</span>
</div>
<div id="block2" class="item">
<span class="tag1 tag">Java</span>
</div>
</div>
While you've already accepted an answer, I felt that this is a question worthy of a plain JavaScript, rather than simply a jQuery, solution. So, with that in mind, I'd like to offer the following approach (which does use some ECMAScript 6 feastures, so does require a fairly modern browser):
// using an Immediately-Invoked Function Expression syntax,
// so that the enclosed function will be executed when
// encountered, rather than requiring the user to call it
// explicitly (this would need to run in a DOMReady callback
// or once the DOM has been constructed, however):
(function hasAll(opts) {
// setting the default settings for the function:
var settings = {
// a CSS Selector string to identify the ancestor
// element that you wish to identify:
'ancestorSelector': 'div',
// an array of CSS Selectors to identify the
// descendants by which the ancestor should
// be found:
'descendantSelectors': []
}
// looking at the named (not inherited) properties
// of the opts Object supplied by the user:
for (var property in opts) {
// if the opts Object has a given property
// name we set the corresponding property
// of the settings Object to be equal to that
// property-value:
if (opts.hasOwnProperty(property)) {
settings[property] = opts[property];
}
}
// finding all the elements represented by the first selector
// of the user-supplied selectors contained within an element
// matching the ancestor selector:
var firstElements = document.querySelectorAll(
settings.ancestorSelector + ' ' + settings.descendantSelectors[0]
),
// converting the NodeList returned by document.querySelectorAll()
// into an Array, using Array.from:
arrayOfFirsts = Array.from(firstElements),
// here we iterate over that Array, using Array.prototype.filter():
hasSiblings = arrayOfFirsts.filter(function(n) {
// we look for the parentNode of the current node (n):
var p = n.parentNode;
// we use Array.prototype.every() to ensure that every
// selector in the descendantSelectors Array returns
// a Node (document.querySelector() returns only the
// first node matching the given selector, or null if
// there is no element matching that selector).
// if Array.prototype.every() returns true (all elements
// of the Array match the supplied test) then the current
// node (n) is retained in the array returned by
// Array.prototype.filter():
return settings.descendantSelectors.every(function(selector) {
// Array.prototype.every() returns a Boolean,
// true : if all elements of the Array match
// the supplied test/assessment,
// false: if *any* of the elements of the Array
// fail to match.
// this is the test that we're matching against:
return p.querySelector(selector) !== null;
});
});
// here we iterate over the hasSiblings Array, and use
// Array.prototype.map() to form a new Array, using
// an Arrow function to take the current node (n)
// and find, and return, the closest element to that
// node which matches the supplied settings.ancestorSelector:
var found = hasSiblings.map(n => n.closest(settings.ancestorSelector));
// returning that array to the calling context:
return found;
})({
// this is the 'opts' Object that we're passing to the
// IIFE-contained function:
'ancestorSelector': '.item',
'descendantSelectors': ['.tagOne', '[data-demo]']
// using Array.prototype.forEach() to iterate over the
// returned elements, to add the class 'hasAll' to the
// the classList (the list of class-names) of the given
// node (n):
}).forEach(n => n.classList.add('hasAll'));
(function hasAll(opts) {
var settings = {
'ancestorSelector': 'div',
'descendantSelectors': []
}
for (var property in opts) {
if (opts.hasOwnProperty(property)) {
settings[property] = opts[property];
}
}
var firstElements = document.querySelectorAll(
settings.ancestorSelector + ' ' + settings.descendantSelectors[0]
),
arrayOfFirsts = Array.from(firstElements),
hasSiblings = arrayOfFirsts.filter(function(n) {
var p = n.parentNode;
return settings.descendantSelectors.every(function(selector) {
return p.querySelector(selector) !== null;
});
});
var found = Array.from( hasSiblings.map(n => n.closest(settings.ancestorSelector)) );
return found;
})({
'ancestorSelector': '.item',
'descendantSelectors': ['.tagOne ~ .tagTwo']
}).forEach(n => n.classList.add('hasAll'));
div {
width: 50%;
margin: 0.5em auto;
border: 2px solid #000;
border-radius: 1em;
padding: 0.5em;
box-sizing: border-box;
}
.hasAll {
border-color: #f90;
}
.hasAll span {
color: #f90;
font-weight: bold;
}
<div id="items">
<div id="block1" class="item">
<span class="tag tagOne">One</span>
<span class="tag tagTwo">Two</span>
</div>
<div id="block2" class="item">
<span class="tag tagOne">Java</span>
</div>
<div id="block3" class="item">
<span class="tag tagOne" data-demo="false">tag-one</span>
<span class="tag tagTwo">tag-two</span>
<span class="tag" data-demo="true">tag-three</span>
</div>
</div>
JS Fiddle demo.
Note that, with the above function, an ancestor element will be matched if any of its descendants', or its descendants' siblings, matches multiple selectors.
References:
Array.from().
Array.prototype.every().
Array.prototype.filter().
Array.prototype.forEach().
Array.prototype.map().
Arrow functions.
document.querySelector().
document.querySelectorAll().
Element.classList.
Element.closest().
for...in statement.
Object.hasOwnProperty().
Node.parentNode.
Try this
blocks.filter('.item').find('[id="tagOne"],[id="tagTwo"]');
Try using has jquery has selector it searches if selected nodes have certain children:
https://jsfiddle.net/96gbf7xg/

Finding the index of elements of the same class but with different parents

Say I have elements of the same class but the're wrapped in different parent divs. How would I go about in finding the index of each .child div?
If I were to invoke the index in an iterator I'd get back 0 since its the one and only .child present within the parent div. What I'm looking to do is get back index of those divs based within the scope of the #container div.
$(function() {
$("#container .child").text(function() {
var i = $(this).index();
return "The index of this element is " + i
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div id="one">
<div class="child"></div>
<div>
<div id="two">
<div class="child"></div>
<div>
<div id="three">
<div class="child"></div>
<div>
</div>
The first argument to your callback function passed to the .text() method is the index within the current set.
$(function() {
$("#container .child").text(function(i) {
return "The index of this element is " + i
});
});
If you want to use index you can use the selector and then pass in the element to get the index in the collection
$("#container .child").index(this)
or even the other way around
$(this).index("#container .child")
but the easiest is to just use the iterator in the callback function for text()
$("#container .child").text(function(i) {
return "The index of this element is " + i
});

Categories