Move child elements if parent is duplicate - javascript

Just staring to lean js here. Is it possible to move child elements from one parent under another parent if parent name is duplicate?
Example:
How to put Something else & Something like under a single Composition parent, and Someone else under one Content, and delete the duplicates.
Is this possible with javascript or jquery? Thank you!
<li>
<h4>Composition</h4>
<p>Something</p>
</li>
<li>
<h4>Composition</h4>
<p>Something else</p>
</li>
<li>
<h4>Composition</h4>
<p>Something like</p>
</li>
<li>
<h4>Content</h4>
<p>Someone</p>
</li>
<h4>Content</h4>
<p>Someone else</p>
</li>
here is the js I got so far, I'm stuck at moving p items to the first element that repeats, each under it's parent. thing is parent elements don't have certain id's or classes:
const $technicalProp = $("[data-surface-prop]");
const technicalVal = $("[data-surface-val]");
let duplicate = {};
$technicalProp.each(function() {
let txt = $(this).text();
if (duplicate[txt]) {
$(this).remove(); // this only removes the dulicate names I also need to move chlidren
}
else {
duplicate[txt] = true;
}
});

yes you can i dont know if this is the best way but this works, my approach is by making an arrayobject (js) as a new model of what you want
the logic is i extract the text of h4 and check if the data variable has been registered there, if not registered yet then i push it to the data and the child text (p)
if registered already then i push the child text only
let data = [];
const lists = document.querySelectorAll('li').forEach( (li, liIdx) => {
let parentName = li.querySelector('h4').innerHTML;
let childName = li.querySelector('p').innerHTML;
let isRegistered = data.find( x => x.parentName === parentName);
if(!isRegistered) {
childName = [childName];
data.push({ parentName, childName});
}else {
console.log(isRegistered)
isRegistered.childName.push(childName);
}
});
and this is what we got
0:
childName: (3) ["Something", "Something else", "Something like"]
parentName: "Composition"
1:
childName: (2) ["Someone", "Someone else"]
parentName: "Content"
then reset the ol tag and rebuild its item
let ol = document.querySelector('ol');
ol.innerHTML = '';
data.map( d => {
let p = d.childName.map( c => `<p>${c}</p>`).join('');
ol.insertAdjacentHTML('beforeend', `
<li>
<h4>${d.parentName}</h4>
${p}
</li>
`);
})

With jQuery you can remove and add elements with detach(), append() and prepend()
Like here: https://stackoverflow.com/a/19802593/11265780 (but replace jQuery with $)

Related

Toggle switch display on selected

I would like to display creating product in admin section sub categories in tree only when selected else closed in woocommerce product categoreis. How can I achieve this ? Presently it appears like this. Tried css but didn't work.
<li id="tire_sizes-52"><label class="radiall"><input value="52" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-52"> 145</label><ul class="children">
<li id="tire_sizes-62"><label class="radiall"><input value="62" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-62"> 65</label> <ul class="children">
<li id="tire_sizes-87"><label class="radiall"><input value="87" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-87"> 15</label></li>
</ul>
</li>
</ul>
</li>
I want it closed and open only if selected
// if a checkbox has a child, add a css class="has-children"
// select them
const parents = document.querySelectorAll('.has-children')
const caches = parents.map(() => ([]))
function clear(parent) {
const id = parent.getAttribute('id');
const ul = parent.querySelector('.children');
ul.css.display = 'none'; // hide from the DOM
// you may also need to remove it from the DOM
/*
children = [];
while (parent.firstChild) {
const firstChild = parent.firstChild
parent.removeChild(firstChild)
caches[id].push(firstChild);
}*/
}
// inital setup
parents.forEach(function (parent) {
const id = parent.getAttribute('id');
clear(parent);
// setup listen for changes to parents
parent.addEventListener('change', function() {
let child;
if (child = parent.querySelector('children')) {
if (child.css.display === 'block') {
clear(parent)
} else {
//caches[this.getAttribute('id')].forEach(c =>
// this.appendChild(c)
delete caches[this.getAttribute('id')]
const ul = parent.querySelector('.children');
ul.css.display = 'block'; // show from bom
}
});
})
something like this. Checkout jquery it may be easier
If this is right, please give me points, i'm a nooob
I haven't executed this code , so it will need your attentions. but that is the essance of it. As I say. look into jquery. This can be accomplished in 3 lines of code.

How do I insert HTML objects between each child object?

Is this function going to be able to insert li elements in the dashed spaces in the following html code?
const shoppingList = document.getElementById("shoppinglist");
var li = document.createElement('li');
for (let i = 0; i < shoppingList.children.length; i++) {
shoppingList.insertBefore(li, shoppingList.children[i]);
if (i == shoppingList.children.length - 1) {
shoppingList.appendChild(li);
}
}
<ul id="shoppinglist" class="collection">
----
<li class="collection-item" id="listitem:Where" draggable=true></li>
----
<li class="collection-item" id="listitem:There" draggable=true></li>
----
</ul>
No, it won't.
You create only one list element and then are trying to insert it multiple times. You have to create a new element in each iteration.
The shoppingList.children is constantly updated within the for loop, therefore the condition inside for(...) declaration is not going to work as you'd expect.
The following code should work. The difference is, that I take references to only existing list items and try to prepend new list items relative to them. Finally I just append a new list item to the whole tree, therefore I fulfill even the condition when none children are present.
const shoppingList = document.getElementById('shoppinglist')
let i = 0
const createLi = () => {
const li = document.createElement('li')
li.innerText = `New ${++i}`
return li
}
Array.from(shoppingList.children)
.forEach(c => shoppingList.insertBefore(createLi(), c))
shoppingList.appendChild(createLi())
<ul id="shoppinglist">
<li>Original 1</li>
<li>Original 2</li>
</ul>

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>`;

Select only the root item of a html node

I have this html code.
<div class="breadcrumb">
Home
<a class="breadcrumb" href="#">About</a>
<a class="breadcrumb" href="#">History</a>
Message from our Founding Members
</div>
Using javascript I want to get the text from the div ".breadcrumb". The problem is the a tag under the div also has a class with the same name, when I run this code:
var names = document.querySelectorAll('.breadcrumb');
return [].map.call(names, function(name) {
return name.textContent;
});
My first element of the array gets the textContent of all the a elements and also the div.
How can I do to get the text of only the div. In this case I want to return only "Message from our Founding Members".
Is there a way to select only the root item of the html, when they have all the same class ?
Thanks
If you want to get the text from the <a> tags with the class="breadcrumb", you can do that by using more specific selectors that include the tag type like this:
var items = document.querySelectorAll("div.breadcrumb a.breadcrumb");
var text = [];
for (var i = 0; i < items.length; i++) {
text.push(items[i].textContent);
}
Working demo: http://jsfiddle.net/jfriend00/kVwH8/
If, what you're trying to do is to get the "Message from our Founding Members" text (I wasn't entirely clear from your original question), then you can do that like this::
var items = document.querySelectorAll("div.breadcrumb a.breadcrumb");
// get node after the last item (that should be the desired text node)
var txtNode = items[items.length - 1].nextSibling;
console.log(txtNode.nodeValue); // Message from our Founding Members
Working demo: http://jsfiddle.net/jfriend00/kynuE/
use div.breadcrumb because that will give you divs with class breadcrumb, not a tags.
You can do this:
var names = document.querySelectorAll('div.breadcrumb')[0].childNodes;
var text = Array.prototype.reduce.call(names,function(prev,node){
if(node.nodeType === 3) return (prev || '' + node.textContent.trim());
});
console.log(text);
There are a lot of ES5 stuff here like trim and reduce so better have those polyfills handy.

Using jQuery to gather all text nodes from a wrapped set, separated by spaces

I'm looking for a way to gather all of the text in a jQuery wrapped set, but I need to create spaces between sibling nodes that have no text nodes between them.
For example, consider this HTML:
<div>
<ul>
<li>List item #1.</li><li>List item #2.</li><li>List item #3.</li>
</ul>
</div>
If I simply use jQuery's text() method to gather the text content of the <div>, like such:
var $div = $('div'), text = $div.text().trim();
alert(text);
that produces the following text:
List item #1.List item #2.List item #3.
because there is no whitespace between each <li> element. What I'm actually looking for is this (note the single space between each sentence):
List item #1. List item #3. List item #3.
This suggest to me that I need to traverse the DOM nodes in the wrapped set, appending the text for each to a string, followed by a space. I tried the following code:
var $div = $('div'), text = '';
$div.find('*').each(function() {
text += $(this).text().trim() + ' ';
});
alert(text);
but this produced the following text:
This is list item #1.This is list item #2.This is list item #3. This is list item #1. This is list item #2. This is list item #3.
I assume this is because I'm iterating through every descendant of <div> and appending the text, so I'm getting the text nodes within both <ul> and each of its <li> children, leading to duplicated text.
I think I could probably find/write a plain JavaScript function to recursively walk the DOM of the wrapped set, gathering and appending text nodes - but is there a simpler way to do this using jQuery? Cross-browser consistency is very important.
Thanks for any help!
jQuery deals mostly with elements, its text-node powers are relatively weak. You can get a list of all children with contents(), but you'd still have to walk it checking types, so that's really no different from just using plain DOM childNodes. There is no method to recursively get text nodes so you would have to write something yourself, eg. something like:
function collectTextNodes(element, texts) {
for (var child= element.firstChild; child!==null; child= child.nextSibling) {
if (child.nodeType===3)
texts.push(child);
else if (child.nodeType===1)
collectTextNodes(child, texts);
}
}
function getTextWithSpaces(element) {
var texts= [];
collectTextNodes(element, texts);
for (var i= texts.length; i-->0;)
texts[i]= texts[i].data;
return texts.join(' ');
}
This is the simplest solution I could think of:
$("body").find("*").contents().filter(function(){return this.nodeType!==1;});
You can use the jQuery contents() method to get all nodes (including text nodes), then filter down your set to only the text nodes.
$("body").find("*").contents().filter(function(){return this.nodeType!==1;});
From there you can create whatever structure you need.
I built on #bobince's terrific answer to make search tool that would search all columns of a table and filter the rows to show only those that matched (case-insensitively) all of a user's search terms (provided in any order).
Here is a screenshot example:
And here is my javascript/jQuery code:
$(function orderFilter() {
// recursively collect all text from child elements (returns void)
function collectTextNodes(element, texts) {
for (
let child = element.firstChild;
child !== null;
child = child.nextSibling
) {
if (child.nodeType === Node.TEXT_NODE) {
texts.push(child);
} else if (child.nodeType === Node.ELEMENT_NODE) {
collectTextNodes(child, texts);
}
}
}
// separate all text from all children with single space
function getAllText(element) {
const texts = [];
collectTextNodes(element, texts);
for (let i = texts.length; i-- > 0; ) texts[i] = texts[i].data;
return texts.join(' ').replace(/\s\s+/g, ' ');
}
// check to see if the search value appears anywhere in child text nodes
function textMatchesFilter(tbody, searchVal) {
const tbodyText = getAllText(tbody).toLowerCase();
const terms = searchVal.toLowerCase().replace(/\s\s+/g, ' ').split(' ');
return terms.every(searchTerm => tbodyText.includes(searchTerm));
}
// filter orders to only show those matching certain fields
$(document).on('keyup search', 'input.js-filter-orders', evt => {
const searchVal = $(evt.target).val();
const $ordersTable = $('table.js-filterable-table');
$ordersTable.find('tbody[hidden]').removeAttr('hidden');
if (searchVal.length <= 1) return;
// Auto-click the "Show more orders" button and reveal any collapsed rows
$ordersTable
.find('tfoot a.show-hide-link.collapsed, tbody.rotate-chevron.collapsed')
.each((_idx, clickToShowMore) => {
clickToShowMore.click();
});
// Set all tbodies to be hidden, then unhide those that match
$ordersTable
.find('tbody')
.attr('hidden', '')
.filter((_idx, tbody) => textMatchesFilter(tbody, searchVal))
.removeAttr('hidden');
});
});
For our purposes, it works perfectly! Hope this helps others!

Categories