Add JSON item into localStorage - javascript

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)

Related

How to look for child elements in a collection

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>

How do I sort HTML Collections based on child elements using plain Javascript?

<div class="transaction-row" id= "transaction-row">
<div class="name">Name 1</div>
<div class="transaction-type"> Category 1</div>
<div class="date">Date 1</div>
<div class="amount"> 1738</div>
<div class="exp-or-inc">Exp or Inc </div>
</div>
<div class="transaction-row" id= "transaction-row">
<div class="name">Name 2</div>
<div class="transaction-type"> Category 2</div>
<div class="date">Date 2</div>
<div class="amount"> 50</div>
<div class="exp-or-inc">Exp or Inc </div>
</div>
What I am trying to do is to sort all of the "transaction row" based on the value in the child element with classname "amount" using javascript. I have been looking for ways to sort multiple dom elements by child elements with sort() and could not find anything.
Edit:
Here is my javascript code:
let transEntries = document.getElementsByClassName("transaction-row");
let sortedEntries = [].slice.call(transEntries);
sortedEntries.sort((a,b)=> {
if(a.children[3] === b.children[3]) {
return 0;
} else {
return (a.children[3] < b.children[3]) ? -1 : 1;
}
});
I just need it to output a console.log of
<div class="amount"> 50</div>
<div class="amount"> 1738</div>
I can figure out how to rearrange the elements. Once I have all the elements stored in an array.
Since you want to sort and arrange the items inside an element .... the best way is to do it with CSS ... use flexbox on parent and order property on its children. Then sort the elements by changing their order using js!
See =>
HTML
<div id="flex">
<div class="transaction-row" id="transaction-row">
<div class="name">
Name 1
</div>
<div class="transaction-type">
Category 1
</div>
<div class="date">
Date 1
</div>
<div class="amount">
1738
</div>
<div class="exp-or-inc">
Exp or Inc
</div>
</div>
<div class="transaction-row" id="transaction-row">
<div class="name">
Name 2
</div>
<div class="transaction-type">
Category 2
</div>
<div class="date">
Date 2
</div>
<div class="amount">
50
</div>
<div class="exp-or-inc">
Exp or Inc
</div>
</div>
</div>
CSS
#flex {
display: flex;
flex-direction: column;
}
.transaction-row {
margin-block: 10px;
}
Js
const main = document.querySelector('#flex')
let main_children = document.querySelectorAll('#flex > *')
main_children = Array.from(main_children)
main_children = main_children.sort((a, b) => {
return parseInt(a.querySelector('.amount').innerText) - parseInt(b.querySelector('.amount').innerText)
})
main_children.forEach((child, index) => child.style.order = index)
console.log(main_children)
Please note CSS flexbox order property isn't supported by screen readers, they will read them in the order as the HTML has written !
Best practice here would be using flexbox .. also never use innerHTML to change the order as it will remove event Listeners and leads to several issues!
As #Teenmu pointed out, assuming you just want to sort out the child elements and all the transaction row share the same parent and that there are no other elements except transaction rows inside their parents, this should work
const transactionRows = [...document.querySelectorAll(".transaction-row")];
let doAllRowsShareSameParent = true;
let lastRowParent = null;
for(row of transactionRows){
if(lastRowParent && row.parentElement !== lastRowParent){
doAllRowsShareSameParent = true;
break;
}
lastRowParent = row.parentElement;
}
const parent = lastRowParent;
if(doAllRowsShareSameParent) {
transactionRows.sort((row1, row2) => {
const row1Amount = parseFloat(row1.querySelector(".amount").innerText);
const row2Amount = parseFloat(row2.querySelector(".amount").innerText);
return row1Amount - row2Amount;
}).map(row => {parent.appendChild(row);});
}
<div class="transaction-row" id= "transaction-row">
<div class="name">Name 1</div>
<div class="transaction-type"> Category 1</div>
<div class="date">Date 1</div>
<div class="amount"> 1738</div>
<div class="exp-or-inc">Exp or Inc </div>
</div>
<div class="transaction-row" id= "transaction-row">
<div class="name">Name 2</div>
<div class="transaction-type"> Category 2</div>
<div class="date">Date 2</div>
<div class="amount"> 50</div>
<div class="exp-or-inc">Exp or Inc </div>
</div>
Hope this helps you out
Update: As #Teenmu pointed out, although the previous solution will work with innerHTML, it will cause loss of event listeners attached to previous rows. I have updated the logic to fix that.
const transactionRows = document.querySelectorAll('.transaction-row');
const sortedRows = Array.from(transactionRows).sort(sortRowsByAmount);
for (let row of sortedRows) {
rowsContainer.append(row);
}
function sortRowsByAmount(row1, row2) {
return getAmount(row1) - getAmount(row2);
function getAmount(row) {
return row.querySelector('.amount').innerHTML.trim();
}
}
I didn't test it But it should work

Src unknown after push

Hi i have problem with pushing item from one array to another. It's system of equipment in my game. The problem is that image of item has unknown src after being pushed to another array.
angular ts
itemy = [{id: 0, name: "sword", url:'../../../../assets/img/sword.png'}, {id: 1, name: "sword2", url:"../../../../assets/img/sword2.png"}];
images = JSON.parse(localStorage.getItem('images')) || [];
add (){
let index=Math.round(Math.random())
this.images.push(this.itemy[index]);
localStorage.setItem('images', JSON.stringify(this.images));
}
eq = JSON.parse(localStorage.getItem('eq')) || [];
select() {
this.eq.push(this.images);
this.eq.length = 1;
localStorage.setItem('eq', JSON.stringify(this.eq));
}
unselect() {
this.eq.pop();
localStorage.setItem('eq', JSON.stringify(this.eq));
}
}
HTML
<div class="eq">
<div id="contentInside2" *ngFor="let image of eq">
<img class="item" src={{image.url}} (click)="unselect()"/> </div>
</div>
<button (click)="add()">Add</button>
<div id="content">
<div id="contentInside" *ngFor="let image of images">
<img class="item" src={{image.url}} (click)="select()"/>
</div>
</div>
Here is how it looks now
If you want to only select clicked image.
Change your template to :-
<div class="eq">
<div id="contentInside2" *ngFor="let image of eq; let i=index">
<img class="item" src={{image.url}} (click)="unselect(i, image)"/> </div>
</div>
<button (click)="add()">Add</button>
<div id="content">
<div id="contentInside" *ngFor="let image of images; let i=index">
<img class="item" src={{image.url}} (click)="select(i, image)"/>
</div>
</div>
then typescript method to :-
select(index, image) {
if(this.eq.length === 1) {
return;
}
this.eq.push(image);
this.images.splice(index, 1);
localStorage.setItem('images', JSON.stringify(this.images));
localStorage.setItem('eq', JSON.stringify(this.eq));
}
unselect(index, image) {
this.eq.splice(index, 1);
this.images.push(image);
localStorage.setItem('images', JSON.stringify(this.images));
localStorage.setItem('eq', JSON.stringify(this.eq));
}

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.

Increment value by index in ngFor - Angular2

I have a problem with incrementing value in ngFor. I have two methods for increment and decrement, the code is below. NgFor is really long so I will post only ts file and HTML which has increment and decrement actions. My ngFor goes like this *ngFor="let product of catalogProducts. The main problem is that when I want to increase the value of input in ngFor, but it updates all inputs in ngFor...
TS file
#Input() counterValue = 0;
increment(index) {
this.counterValue++;
}
decrement() {
if(this.counterValue <= 0) return false;
this.counterValue--;
}
HTML
<div class="incrementer">
<div class="column">
<button (click)="decrement()">
-
</button>
</div>
<div class="column">
<input class="count" [value]="counterValue" [(ngModel)]="product.quantity" />
</div>
<div class="column">
<button (click)="increment(index)">
+
</button>
</div>
</div>

Categories

Resources