How to remove duplicate childNode elements within a parent in Javascript? - 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>

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).

Cannot get value of div

I have problem with getting value of a div with class="idC". I need this value to remove object from my array. I know I probably gonna need to use parseInt but all I'm getting with this code is rowid: undefined when I use console.log("rowid: " + bikeId.value). bikeRow.remove(); works fine. It removes the row I want to.
const buttonDel = document.querySelectorAll(".deleteC");
buttonDel.forEach(button => {
console.log("jazda");
button.addEventListener('click', () => {
const bikeRow = button.parentNode;
const bikeId = bikeRow.firstChild;
if (!window.confirm())
return;
console.log("rowid: " + bikeId.value);
bikeRow.remove();
//bikeStorage.removeBike(bikeId.value);
})
})
<div class="bikeRows">
<div class="bikeRow">
<div class="idC"></div>${bike.id}</div>
<div class="frameC">${bike.frame}</div>
<div class="suspC">${bike.susp}</div>
<div class="wheelC">${bike.wheel}</div>
<div class="typeC">${bike.constructor.name}</div>
<div class="deleteC"><button class="delButton" id="${bike.id}">Delete</button></div>
</div>
</div>
You have invalid HTML and you really should delegate
I added the ID as a data-attribute to the row like this
<div class="bikeRow" data-id="${bike.id}">
Makes the code much simpler to debug and extend
document.querySelector(".bikeRows").addEventListener("click", e => {
const tgt = e.target.closest("button");
if (!tgt.matches(".delButton")) return; // not a delete button
if (!window.confirm("Sure?")) return; // they cancelled
const bikeRow = tgt.closest("div.bikeRow"),
id = bikeRow.dataset.id; // the ID to remove from the storage
bikeRow.remove();
//bikeStorage.removeBike(id);
})
<div class="bikeRows">
<div class="bikeRow" data-id="ID1">
<div class="idC">ID1</div>
<div class="frameC">Frame 1</div>
<div class="suspC">Susp 1</div>
<div class="wheelC">Wheel 1</div>
<div class="typeC">Constructor name 1</div>
<div class="deleteC"><button class="delButton">Delete</button></div>
</div>
<div class="bikeRow" data-id="ID2">
<div class="idC">ID 2</div>
<div class="frameC">Frame 2</div>
<div class="suspC">Susp 2</div>
<div class="wheelC">Wheel 2</div>
<div class="typeC">Constructor name 2</div>
<div class="deleteC"><button class="delButton">Delete</button></div>
</div>
<div class="bikeRow" data-id="ID3">
<div class="idC">ID 3</div>
<div class="frameC">Frame 3</div>
<div class="suspC">Susp 3</div>
<div class="wheelC">Wheel 3</div>
<div class="typeC">Constructor name 3</div>
<div class="deleteC"><button class="delButton">Delete</button></div>
</div>
</div>

how to querySelector children (only) from an element?

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>

How can I sort divs with javascript

I would like to sort a few divs in ascending order based on their data-id. How can I do that?
<div class="container" data-id="1000">
<div id="H1"></div>
<div id="sub">sub 1</div>
<div id="sub">sub 2</div>
</div>
<div class="container" data-id="3000">
<div id="H1"></div>
<div id="sub"></div>
<div id="sub"></div>
</div>
<div class="container" data-id="2000">
<div id="H1"></div>
<div id="sub"></div>
<div id="sub"></div>
</div>
I've found the solution to my problem a while ago:
function sortOut() {
// get the classname chapcontainer
var classname = document.getElementsByClassName('container');
// create a variable and put the classes it into an array.
var divs = [];
for (var i = 0; i < classname.length; ++i) {
divs.push(classname[i]);
}
// Sort the divs based on data-id.
divs.sort(function(a, b) {
return +a.getAttribute("data-id") - +b.getAttribute("data-id");
});
};
divs.sort does the trick. More info about this function can be found here:
https://www.w3schools.com/jsref/jsref_sort.asp

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