how to querySelector children (only) from an element? - javascript

Imagine a structure like this:
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>
Now if I write:
document.querySelectorAll('#boxes > div:not([class])')
I will get #firstdiv and #lastdiv. Great.
But what if I want to do the same from the #boxes element?
const boxes = document.querySelector('#boxes')
// this is what I tried
boxes.querySelectorAll('> div:not([class])') // it doesn't work
// and if I type
boxes.querySelectorAll('div:not([class])')
// I get #firstdiv, #lastdiv BUT ALSO #subdiv which I don't want
How can I do that?

You can use :scope to reference the element the querySelectorAll is being called on:
const collection = boxes.querySelectorAll(':scope > div:not([class])');
console.log(collection.length, collection[0], collection[1]);
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>
(unfortunately, it doesn't have great browser support, though)

Aside from :scope which sadly doesn't have good browser support, several options for you in two main categories:
Filtering children
You could just filter children using matches and Array.prototype.filter:
const collection = Array.prototype.filter.call(boxes.children, child => child.matches("div:not([class])"));
Live Example:
const collection = Array.prototype.filter.call(boxes.children, child => child.matches("div:not([class])"));
for (const child of collection) {
console.log(child.id);
}
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>
That does have to parse the selector each time. If it's really as simple as the selector shown, you might just write the parts individually:
const collection = Array.prototype.filter.call(
boxes.children,
child => child.tagName === "DIV" && child.getAttribute("class") === null
);
Live Example:
const collection = Array.prototype.filter.call(
boxes.children,
child => child.tagName === "DIV" && child.getAttribute("class") === null
);
for (const child of collection) {
console.log(child.id);
}
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>
If you're okay with allowing it to match <div id="foo" class> elements (e.g., they have the attribute, but it's empty), then it's a bit simpler:
const collection = Array.prototype.filter.call(
boxes.children,
child => child.tagName === "DIV" && !child.className
);
Live Example:
const collection = Array.prototype.filter.call(
boxes.children,
child => child.tagName === "DIV" && !child.className
);
for (const child of collection) {
console.log(child.id);
}
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>
Filtering querySelectorAll result
Or, filter out the matches you don't want after the fact:
const collection = Array.prototype.filter.call(
boxes.querySelectorAll("div:not([class])"),
child => child.parentNode === boxes
);
Live Example:
const collection = Array.prototype.filter.call(
boxes.querySelectorAll("div:not([class])"),
child => child.parentNode === boxes
);
for (const child of collection) {
console.log(child.id);
}
<div id="boxes">
<div id="firstdiv">...</div>
<div class="important">...</div>
<div id="lastdiv">
<div id="subdiv">...</div>
<div class="important">...</div>
</div>
</div>

Related

Filter html elements based on data attribute

I have the following html structure
<div id="container">
<div id="child_1" data-customId="100">
</div>
<div id="child_2" data-customId="100">
</div>
<div id="child_3" data-customId="100">
</div>
<div id="child_4" data-customId="20">
</div>
<div id="child_5" data-customId="323">
</div>
<div id="child_6" data-customId="14">
</div>
</div>
And what I want to do is to get the count of child divs that contains different data attribute. For example, I'm trying this:
$(`div[id*="child_"]`).length); // => 6
But that code is returning 6 and what I want to retrieve is 4, based on the different data-customId. So my question is, how can I add a filter/map to that selector that I already have but taking into consideration that is a data-attribute.
I was trying to do something like this:
var divs = $(`div[id*="child_"]`);
var count = divs.map(div => div.data-customId).length;
After you getting the child-divs map their customid and just get the length of unique values:
let divs = document.querySelectorAll(`div[id*="child_"]`);
let idCustoms = [...divs].map(div=>div.dataset.customid);
//idCustoms: ["100", "100", "100", "20", "323", "14"]
//get unique values with Set
console.log([... new Set(idCustoms)].length);//4
//or with filter
console.log(idCustoms.filter((item, i, ar) => ar.indexOf(item) === i).length);//4
<div id="container">
<div id="child_1" data-customId="100">
</div>
<div id="child_2" data-customId="100">
</div>
<div id="child_3" data-customId="100">
</div>
<div id="child_4" data-customId="20">
</div>
<div id="child_5" data-customId="323">
</div>
<div id="child_6" data-customId="14">
</div>
</div>
Note: $ is equivalent to document.querySelectorAll in js returns a NodeList that's why I destructure it by the three dots ...
You'll have to extract the attribute value from each, then count up the number of uniques.
const { size } = new Set(
$('[data-customId]').map((_, elm) => elm.dataset.customid)
);
console.log(size);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<div id="child_1" data-customId="100">
</div>
<div id="child_2" data-customId="100">
</div>
<div id="child_3" data-customId="100">
</div>
<div id="child_4" data-customId="20">
</div>
<div id="child_5" data-customId="323">
</div>
<div id="child_6" data-customId="14">
</div>
</div>
No need for jQuery for something this trivial, though.
const { size } = new Set(
[...document.querySelectorAll('[data-customId]')].map(elm => elm.dataset.customid)
);
console.log(size);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<div id="child_1" data-customId="100">
</div>
<div id="child_2" data-customId="100">
</div>
<div id="child_3" data-customId="100">
</div>
<div id="child_4" data-customId="20">
</div>
<div id="child_5" data-customId="323">
</div>
<div id="child_6" data-customId="14">
</div>
</div>
Note that the property customid is lower-cased in the JavaScript. This could be an easy point of confusion. You might consider changing your HTML from
data-customId="14"
to
data-custom-id="14"
so that you can use customId in the JS (to follow the common conventions).

what is the fast and the easiest way to change the order of divs in html with javascript

I want to move div1 and div2 to the end of list
with only vanilla javascript
<div class="container">
<div id="chlid-1"></div>
<div id="chlid-2"></div>
<div id="chlid-3"></div>
<div id="chlid-4"></div>
<div id="chlid-5"></div>
</div>
Use CSS flex on a parent, and the order property on the desired child elements
.container {
display: flex;
flex-direction: column;
}
#child-1,
#child-2 {
order: 1;
}
<div class="container">
<div id="child-1">1</div>
<div id="child-2">2</div>
<div id="child-3">3</div>
<div id="child-4">4</div>
<div id="child-5">5</div>
</div>
Using JavaScript - use the Element.append() method like:
// DOM utils:
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
const EL = (sel, par) => (par || document).querySelector(sel);
// Task:
EL(".container").append(...ELS("#child-1, #child-2"));
<div class="container">
<div id="child-1">1</div>
<div id="child-2">2</div>
<div id="child-3">3</div>
<div id="child-4">4</div>
<div id="child-5">5</div>
</div>
You can do the following and the old div will be removed automatically
var element = document.getElementById('child1');
var parent = element.parentNode;
parent.insertAfter(element, parent.lastChild);

How to remove duplicate childNode elements within a parent in Javascript?

How do you remove any duplicate childNodes from a parent element so that there is never more than 1 element with the same innerText within the parent? HTML example below of what the before and the intended after is.
HTML
<div id="parent">
<div class="child">hello</div>
<div class="child">hello</div>
<div class="child">world</div>
<div class="child">world</div>
</div>
Goal
<div id="parent">
<div class="child">hello</div>
<div class="child">world</div>
</div>
Try this.
var children = document.querySelectorAll(".child")
var tmpTexts = []
for (const c of children) {
if (tmpTexts.includes(c.innerText)) continue
tmpTexts.push(c.innerText)
c.parentNode.removeChild(c)
}
<div id="parent">
<div class="child">hello</div>
<div class="child">hello</div>
<div class="child">world</div>
<div class="child">world</div>
</div>
Here’s another way of doing it:
const children = document.querySelectorAll('.child');
function filterChildren(text, i, textArray) {
if ( textArray.indexOf(text) <= textArray.lastIndexOf(text) && textArray.indexOf(text) !== i ) {
children[i].parentNode.removeChild( children[i] )
}
}
Array
.from(children)
.map( child => child.innerHTML )
.forEach(filterChildren);
<div id="parent">
<div class="child">hello</div>
<div class="child">hello</div>
<div class="child">world</div>
<div class="child">world</div>
</div>

get data from html page using javascript

I have some HTML - pretty nasty, but not mine and so I don't have control over it. I need to extract some data from the form, the First name value (ABDIGANI) and the Surname value (AHMED). What is the best way to do this with javascript?
<div class="voffset3"></div>
<div class="container well panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-3">
<span class="ax_paragraph">
First name
</span>
<div class="form-group">
<div class="ax_h5">
ABDIGANI
</div>
</div>
</div>
<div class="col-md-3">
<span class="ax_paragraph">
Surname
</span>
<div class="form-group">
<div class="ax_h5">
AHMED
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You could consider HTML in most cases well structured. Try this the following snippet.
Edit: did a change due to the first comment.
Edit: if you have more than one rows, you should use
document.querySelectorAll('.container > .panel-body > .row');
and fetch the pairs for each found element as below.
const markers = ['First name', 'Surname'];
const mRx = [new RegExp(markers[0]), new RegExp(markers[1])];
function findMarker(element) {
for(let i = 0; i < mRx.length; i++) {
if(element.innerHTML.match(mRx[i])) {
return markers[i];
}
}
return null;
}
function findValue(el) {
return el.parentElement.querySelector('.form-group > div').innerHTML.trim();
}
const pairs = [... document.querySelectorAll('.ax_paragraph')]
.map(el => {
return {el: el, mk: findMarker(el)};
})
.filter(n => n.mk !== null)
.map(o => {
return {key: o.mk, value: findValue(o.el)};
});
console.log(pairs);
var x = document.querySelectorAll(".panel-body > div >.col-md-3 > div > div");
x.forEach(myFunction);
function myFunction(item, index) {
//console.log(item.innerHTML.trim());
if (index===0){
console.log("First name : "+item.innerHTML.trim());
}
if (index===1){
console.log("Surname : "+item.innerHTML.trim());
}
}
<div class="voffset3"></div>
<div class="container well panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-3">
<span class="ax_paragraph">
First name
</span>
<div class="form-group">
<div class="ax_h5">
ABDIGANI
</div>
</div>
</div>
<div class="col-md-3">
<span class="ax_paragraph">
Surname
</span>
<div class="form-group">
<div class="ax_h5">
AHMED
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Check this
const firstName = document.querySelector('.row .form-group div').textContent.trim();
const surname = document.querySelector('.row > div:last-child .form-group div').textContent.trim();
note: Its better to change html according to functionality needs, like if you need firstname then you must keep an id attribute to div which is having first name, same goes to surname. then select those fields using id selector, because even if you change html page structure in future, functionality will not get effected.
Check below for reference on how the html should actually be(just to make sure you know it, but the solution you are seeking is above in first two lines)
eg:
<div class="voffset3"></div>
<div class="container well panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-3">
<span class="ax_paragraph">
First name
</span>
<div class="form-group">
<div class="ax_h5" id="firstNameField">
ABDIGANI
</div>
</div>
</div>
<div class="col-md-3">
<span class="ax_paragraph">
Surname
</span>
<div class="form-group">
<div class="ax_h5" id="surnameField">
AHMED
</div>
</div>
</div>
</div>
</div>
</div>
</div>
document.querySelector('.form-group > div').textContent but without modifying the html there is no way to distinguish first name and surname.
If you can't edit the HTML, you can use the XPATH for Example.

Dynamicaly change class name of same class by adding number

One final problem..how to dynamicaly change class name of same instruments by adding number?
<div class="score">
<div class="system">
<div class="stff_Flute"></div>
<div class="stff_Flute"></div>
<div class="stff_Clarinet"></div>
</div>
<div class="system">
<div class="stff_Flute"></div>
<div class="stff_Flute"></div>
<div class="stff_Clarinet"></div>
</div>
</div>
To this?...
<div class="score">
<div class="system">
<div class="stff_Flute_1"></div>
<div class="stff_Flute_2"></div>
<div class="stff_Clarinet"></div>
</div>
<div class="system">
<div class="stff_Flute_1"></div>
<div class="stff_Flute_2"></div>
<div class="stff_Clarinet"></div>
</div>
</div>
I have this code https://jsfiddle.net/7cLoxn29/1/ but something is wrong...
I'm not terribly fond of jQuery, so I created a vanilla JS solution for you (hopefully that's OK!):
let parents = document.querySelectorAll(".system")
parents.forEach((parent) => {
let children = parent.querySelectorAll("div")
children = Array.from(children).reduce((accumulator, current) => {
if (current.className in accumulator) {
accumulator[current.className].push(current)
} else {
accumulator[current.className] = [current]
}
return accumulator
}, {})
for (var key in children) {
if (children[key].length > 1) {
children[key].forEach((child, i, target) => {
child.className = `${child.className}_${i+1}`
})
}
}
})
Note that this is ES2015 JS code.
Here's an updated fiddle: https://jsfiddle.net/7cLoxn29/5/

Categories