How to look for child elements in a collection - javascript

im very new to javascript and probably thats a silly question. What I am trying to achieve is to loop through rows of a "table", get the innerHTML of specific child nodes and multiply them together.
The html looks like this:
<div class="parent">
...
<div class="countChild">
<div class="container">
<span class="count">5</span>
</div>
</div>
<div class="valueChild">
<span class="value">30</span>
</div>
...
</div>
<div class="parent">
...
<div class="countChild">
<div class="container">
<span class="count">2</span>
</div>
</div>
<div class="valueChild">
<span class="value">30</span>
</div>
...
</div>
To be specific: I want to get both the values inside the'countChild' and the 'valueChild'. In this example those are 5 and 30 for the first row and for the second row its 2 and 30. Then perform a muiltiplication.
What I tried to do is to get all the parent nodes and then iterating through them to get the child nodes.
const parents = document.getElementsByClassName('parent');
for(var row in parents) {
var count = row.getElementsByClassName('countChild').lastChild.innerHTML;
var value = row.getElementsByClassName('valueChild').lastChild.innerHTML;
....
}
However the debugger already throws an error when im trying to get the childs. The error message is row.getElemenstByClassName is not a function. I guess the collection cannot be used like this and my understanding of how to use js to get information from the document is wrong.
Edit: This is what the tree looks like
<div class="listing-entry">
<div class="value-container d-none d-md-flex justify-content-end">
<div class="d-flex flex-column">
<div class="d-flex align-items-center justify-content-end">
<span class="font-weight-bold color-primary small text-right text-nowrap">30</span>
</div>
</div>
</div>
<div class="count-container d-none d-md-flex justify-content-end mr-3">
<span class="item-count small text-right">5</span>
</div>
</div>

You should access parents like an array (not really array but you can cast it to one). Btw, I encourage you to use querySelectorAll and querySelector instead of getElementsByClassName
const parents = document.querySelectorAll(".parent")
parents.forEach(function(row) {
var countChild = row.querySelector(".countChild")
var valueChild = row.querySelector(".valueChild")
var count = countChild ? countChild.innerText : 0
var value = valueChild ? valueChild.innerText : 0
console.log(count, value, count * value)
})
<div class="parent">
...
<div class="countChild">
<div class="container">
<span class="count">5</span>
</div>
</div>
<div class="valueChild">
<span class="value">30</span>
</div>
...
</div>
<div class="parent">
...
<div class="countChild">
<div class="container">
<span class="count">2</span>
</div>
</div>
<div class="valueChild">
<span class="value">30</span>
</div>
...
</div>
Edit: I'm using querySelector instead of getElementsByClassName, and checking if child exists before accessing its innerText property.
Edit: here's a function to get all text nodes under a specific node. Then you can combine them and trim the result to get the value you want.
function textNodesUnder(node) {
var all = [];
for (node = node.firstChild; node; node = node.nextSibling) {
if (node.nodeType == 3) {
all.push(node);
} else {
all = all.concat(this.textNodesUnder(node));
}
}
return all;
}
var nodes = textNodesUnder(document.querySelector(".listing-entry"))
var texts = nodes.map(item => item.nodeValue.trim())
console.log(texts)
<div class="listing-entry">
<div class="value-container d-none d-md-flex justify-content-end">
<div class="d-flex flex-column">
<div class="d-flex align-items-center justify-content-end">
<span class="font-weight-bold color-primary small text-right text-nowrap">30</span>
</div>
</div>
</div>
<div class="count-container d-none d-md-flex justify-content-end mr-3">
<span class="item-count small text-right">5</span>
</div>
</div>

Related

changing Div order in a div main container, javascript DOM manipulation

i want to move a div form the start to the end in a the same div:from 1-2-3 to 2-3-1
my code
const cards = document.querySelectorAll(".card");
const firstCard = document.querySelectorAll(".card")[0].innerHTML;
cards[0].remove();
document.getElementById("mainC").appendChild(firstCard);
<div id="mainC">
<div class="card"> 1 </div>
<div class="card"> 2 </div>
<div class="card"> 3 </div>
</div>
i want to move a div form the start to the end in a the same div:from 1-2-3 to 2-3-1
Based on your original code,we need to remove .innerHTML,then it will work
const cards = document.querySelectorAll(".card");
const firstCard = document.querySelectorAll(".card")[0];// remove .innerHTML and it will work
cards[0].remove();
document.getElementById("mainC").appendChild(firstCard);
<div id="mainC">
<div class="card"> 1 </div>
<div class="card"> 2 </div>
<div class="card"> 3 </div>
</div>
Another solution is to store the content into an array and change the array element order
let divs = []
document.querySelectorAll('#mainC .card').forEach(d =>{
divs.push(d.outerHTML)
})
divs.push(divs.shift())
document.querySelector('#mainC').innerHTML = divs.join('')
<div id="mainC">
<div class="card"> 1 </div>
<div class="card"> 2 </div>
<div class="card"> 3 </div>
</div>
you have used document.querySelectorAll(".card")[0].innerHTML which gives '1' which is not type "node" so it will give an error when appending as a child.
remove .innerHTML and it will work
here is an example that removes the first child and append it to the end.
const shuffle = () => {
const parent = document.querySelector("#mainContainer");
const childrens = [...parent.children];
parent.appendChild(childrens.splice(0,1)[0]);
};
<button type="button" onclick=shuffle()> suffel</button>
<div id="mainContainer">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
</div>

Add JSON item into localStorage

I'm working on an angular task and the goal is to add items to be purchase into localStorage before adding to cart.
I have four different object that users can add, item can be added several time, so the general condition is if object exist on localStorage and user add it other time I should update quantity attribute, if not add new object with new attribute quantity = 1.
here is the service :
addGiftToCard(type) {
let cardParse = JSON.parse(localStorage.getItem('cart')) || []
let index = _.findIndex(cardParse, item => item.type && item.id == type.id)
if (index == -1) {
type.qte = 1
cardParse.push(type)
} else {
cardParse[index].qte += 1
}
localStorage.setItem('cart', JSON.stringify(cardParse))
}
and the function in my component :
addGiftToCart(type) {
let index = this.cartService.addGiftToCard({ type: type })
}
html :
<div class="available-checks" *ngIf="!(types === null)">
<div class="row checks-list">
<div class="col-md-3" *ngFor="let type of types">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-8 logo-container justify-content-center">
<img class="card-logo" src="../../../assets/images/logo-gold.svg" alt="">
</div>
<div class="col-4 icon-container justify-content-center text-center">
<img [src]=" serverUrl+'/'+type?.image" alt="" style="width: 50px;height: 50px;">
</div>
</div>
<div class="row desc-title-c">
<p class="desc-title">Chèque cadeau</p>
</div>
<div class="row type-c">
<p class="check-type">{{type.type}}</p>
</div>
<div class="offer-container">
<p class="offer">{{type.designation}}</p>
<p class="price">{{type.amount}}€</p>
</div>
<div class="">
<button class="btn btn-footer" (click)="addGiftToCart(type)">Ajouter au panier</button>
</div>
</div>
</div>
</div>
</div>
</div>
The problem I got here is the one object added what ever the item is and only the quantity attribute counter updated and got json format like that :
and If I set type = type.type in the service function It works and I got json like that
I need the json element type do not contain an attribute qty, qty should be outside the object attributes like the first image !
I think you should have used item.type.id instead of item.id like below
let index = _.findIndex(cardParse, item => item.type && item.type.id == type.id)

Jquery .clone() only works once

I'm building a menu ordering app where I have to use Jquery.
I'm using the clone() method to duplicate cart items with the necessary data ejected in. It works once, and then logs and empty object with a <prototype>.
What I am cloning is a section in my HTML I am using as a template with an id to keep it hidden. I remove this on the cloned items.
The ejecting of the data I have excluded as it is working fine and the functions are in other files, but I am open to the idea of them being the cause.
HTML:
<div class="cart-item" id="cartitem-template">
<div class="left">
<div class="image"></div>
<div class="price"></div>
</div>
<div class="mid">
<div class="name">
</div>
<div class="stars">
<span></span>
</div>
<div class="underline"></div>
<div class="toggleReviews">
<span></span>
</div>
</div>
<div class="right">
<div class="remove-cart-item">✕</div>
</div>
</div>
</div>
The JS function:
buildCartItem = function(menuItem) {
const template = $("#cartitem-template")
template
.removeAttr("id")
.clone(false)
const newCartItem = template
newCartItem.insertAfter(".cart-item")
console.log(newCartItem)
//Get object and index atts from clicked menu item
//Also set that same data into the dom of the cart item
//As well as set that data to local storage
...
// Apply data to the cloned cart item template
newCartItem.find(".image").css("background-image", "url(" + data.image + ")")
setData(".price", "$"+data.price, newCartItem)
...
}
$(".menu-item").click(function() {
buildCartItem($(this))
})
I'm I using .clone() correctly? Honestly stuck
You are removing the attribute "id" from the source element even before cloning, That's why in the subsequent method invocations it could not find an element with the id "cartitem-template". So in your method buildCartItem, remove "id" after cloning.
const newCartItem = template.clone(false).removeAttr("id");
var buildCartItem = function(menuItem) {
const newCartItem = $("#cartitem-template").clone(false).removeAttr("id");
//newCartItem.find(".image").css("background-image", "url(" + data.image + ")");
//setData(".price", "$" + data.price, newCartItem);
newCartItem.insertAfter("#cartitem-template");
}
$(".menu-item").click(function() {
buildCartItem($(this))
})
#cartitem-template {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="cart-item" id="cartitem-template">
<div class="left">
<div class="image">image</div>
<div class="price">price</div>
</div>
<div class="mid">
<div class="name">name
</div>
<div class="stars">
<span>0</span>
</div>
<div class="underline"></div>
<div class="toggleReviews">
<span></span>
</div>
</div>
<div class="right">
<div class="remove-cart-item">✕</div>
</div>
</div>
<button class="menu-item">Clone</button>
i had similar problem and i solved using function:
function getClone(){
return $(myCloningDiv).clone()
}

How to stop all items from being opened when editing item in ngFor loop

I have an array of objects and you can edit the name of each one but then I click to edit one all of the names of the items open, I am wondering how do to fix this.
<div *ngFor="let stop of fave; let i = index" attr.data="{{stop.Type}}">
<div class="card m-1">
<div class="card-body">
<div class="card-text">
<div class="row">
<label class="name" *ngIf="!toggleName" (click)="toggleName = true">{{stop.Name}}</label>
<div class="md-form" *ngIf="toggleName">
<input (keydown.enter)="updateStopName(i, stop.id); toggleName = false" placeholder="Chnage Stop Name" [(ngModel)]="stopName" required mdbInput type="text"
id="form1" class="form-control">
</div>
</div>
<div class="custom">
<img *ngIf="stop.Type === 'Train'" class="train-icon" style="width: 40px; height:40px"
src="assets/img/icon_trian.png" />
<img *ngIf="stop.Type === 'bus'" style="width: 40px; height:40px" src="assets/img/icon_bus.png" />
<img *ngIf="stop.Type === 'Luas'" style="width: 40px; height:40px"
src="assets/img/icon_tram.png" />
</div>
<label class="col-4 custom-label">Stop</label>
<label class="col-5 custom-service-label">Service</label>
<div class="row">
<span class="col-5 stop"> {{stop.StopNo}}</span>
<span style="padding-left:31%;" class="col-6 stop"> {{stop.Type | titlecase}}</span>
</div>
<hr />
<div class="row">
<div class="panel col-7" (click)="getRealtimeInfo({stop: stop.StopNo, type: stop.Type})">
<img class="panel-realtime" src="assets/img/icon_view.png" />
</div>
<div class="panel col-5" (click)="deleteFav(stop.id, i)">
<img class="panel-remove" src="assets/img/icon_remove.png" />
</div>
</div>
</div>
</div>
</div>
</div>
I know its something to do with the index but I am not sure how to write the code to only open the one I clicked on
As you can see at the moment all of them open any help is very much appreciated
If you want to open one at a time, you can use the index and of the item and a boolean. When clicked, set the index value to toggl if it's not already assigned, else assign it null (so that we can close the opened div on same click), and then show the content you want, when toggl === i. Something like:
<div *ngFor="let stop of fave; let i = index">
<label (click)="toggl === i ? toggl = null : toggl = i">Stuff!</label>
<div *ngIf="toggl === i">
<!-- ... -->
</div>
</div>
DEMO: StackBlitz
In your component declare one array
hideme=[];
In your html
<div *ngFor="let stop of fave; let i = index" attr.data="{{stop.Type}}">
<a (click)="hideme[i] = !hideme[i]">show/hide</a>
<div [hidden]="hideme[i]">The content will show/hide</div>
</div>
You have a unique id value inside your array, then you can do it like this:
<div *ngFor="let row of myDataList">
<div [attr.id]="row.myId">{{ row.myValue }}</div>
</div>
Assign an id to your input fields and they will work fine. Right now all of them have same id.
Use this code below as an example:
In your component, create a mapping like so:
itemStates: { [uniqueId: string]: boolean } = {};
Within your on click function:
itemClicked(uniqueId: string) {
let opened: boolean = this.itemStates[uniqueId];
if (opened !== undefined) {
opened = !opened; // Invert the result
} else {
opened = true;
}
}
In your HTML:
<div *ngFor="let item of items">
<h1 (click)="itemClicked(item.uniqueId)">{{ item.name }}</h1>
<div *ngIf="itemStates[item.uniqueId] == true">
<p>This item is open!</p>
</div>
</div>
Essentially, each item in your array should have a unique identifier. The itemStates object acts as a dictionary, with each unique ID having an associated true/false value indicating whether or not the item is open.
Edit: The accepted answer to this question is very simple and works great but this example may suit those who need to have the ability to have more than one item open at once.

Using class tree to delete specific HTML elements

How can I use vanilla JS to find and delete elements with a specific class X where the parent has class Y?
Example. Given
<div class="likes noise1">
<div class="count noise2">
42
</div>
</div>
<div class="retweets noise3">
<div class="count noise4">
7
</div>
</div>
<div class="messages noise5">
<div class="count noise6">
2
</div>
</div>
I would like to delete the first two ".count" elements (the childs of ".likes" and ".retweets"). The messages div however should be left untouched.
I have tried using querySelectorAll which return a frozen NodeList and iterating it, without success.
You can loop through all the elements to check the Element.className property of the Node.parentNode to remove the element like the following way:
document.querySelectorAll('.count').forEach(function(el){
var classN = el.parentNode.className
if(classN.includes('likes') || classN.includes('retweets'))
el.remove();
});
<div class="likes">
<div class="count">
42
</div>
</div>
<div class="retweets">
<div class="count">
7
</div>
</div>
<div class="messages">
<div class="count">
2
</div>
</div>
OR: You can simply simply specify both the classes as part of the selector, in which case you do not need to check the parentNode as the selector will give you only the elements inside the parents:
document.querySelectorAll('.likes > .count, .retweets > .count').forEach(function(el){
el.parentNode.remove();
});
<div class="likes">
<div class="count">
42
</div>
</div>
<div class="retweets">
<div class="count">
7
</div>
</div>
<div class="messages">
<div class="count">
2
</div>
</div>
Another alternative, further to those already given is to keep an array of the css selector you'll need to find your targets. From there, it's just a simple matter of using querySelector so that the result is still live, albeit in a loop.
"use strict";
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onWindowLoaded, false);
function onWindowLoaded(evt)
{
var tgtSelectors = [ '.likes > .count', '.retweets > .count' ];
tgtSelectors.forEach(removeBySelector);
}
function removeBySelector(curSelector)
{
var tgt = document.querySelector(curSelector);
while (tgt != undefined)
{
tgt.remove();
tgt = document.querySelector(curSelector);
}
}
<div class="likes">
<div class="count">42</div>
</div>
<div class="retweets">
<div class="count">7</div>
</div>
<div class="messages">
<div class="count">2</div>
</div>

Categories

Resources