Get the current element running in ngFor - javascript

List of images are executing in a loop(ngFor). I am trying to get the current element that is being clicked by calling the function (click)="addStyle()".The issue I am facing here is ,if i am adding a style [class.active] depending upon the clicked method,it gets reflected in all the image tags.But I am trying to add style to only the particular image being clicked.
component.html
<div class="item" *ngFor="let i of img;let i = index">
<img class="product-i-2" [src]="i" [class.active]="isActive" (click)="addStyle()">
</div>
component.ts
isActive:false;
addStyle()
{
this.isActive=true;
}
component.css
.active
{
border:1px solid red;
}
In short,I am trying to add style to only particular image tag and not all the images in the loop

Try this one.
<div class="item" *ngFor="let i of img;let i = index">
<img class="product-i-2" [src]="i" [class.active]="i === activeIdx"
(click)="addStyle(i)">
</div>
activeIdx: number;
addStyle(i)
{
this.activeIdx = i;
}

you can try like this
HTML
<div class="item" *ngFor="let i of img;let j = index">
<img class="product-i-2" [src]="i" [class.active]="isActive[i] == true ? 'active' : '' " (click)="addStyle(j)">
</div>
TS
isActive: any[] = false;
ngOnInit() {
// here we set defalut value to false
for (let i = 0; i < img.length; i++) {
this.isActive.push(false);
}
}
addStyle(j)
{
this.isActive[j]= !this.isActive[j];
}

You can pass event parameter on click and in that you will get element and than you can add class into it.
<div class="item" *ngFor="let i of img;let i = index">
<img class="product-i-2" [src]="i" (click)="addStyle(e)">
</div>
// In TS file
addStyle(event)
{
event.target.classList.add(className);
}

The best way is you convert the array of images to array of object of images and mark as active.
ngOnInit(){
this.img.forEach((img)=>{
this.alter.push({src:img, isActive:false})
})
}
addAlterStyle(index){
this.alter.forEach((img) => img.isActive = false);
this.alter[index].isActive = true;
}
HTML
<div class="item" *ngFor="let image of alter;let i = index">
<img class="product-i-2" [ngClass]="{'isActive':image.isActive}" [src]="image.src" (click)="addAlterStyle(i)">
</div>
If you dont want to change the img array structure
TS
addStyle(event){
let imageTags = document.querySelectorAll('.image');
imageTags.forEach((element)=>{
element.classList.remove('isActive')
})
event.target.classList.add('isActive')
}
HTML
<div class="item" *ngFor="let image of img;let i = index">
<img class="product-i-2 image" [src]="image" (click)="addStyle($event)">
</div>
Here is the stackblitz working demo

don't save isActive property save active Image and then you can check a condition by reference
activeImg:any;
addStyle($event){
this.activeImg=$event;
}
<div class="item" *ngFor="let i of img;let i = index">
<img class="product-i-2" [src]="i" [class.active]="activeImg==i" (click)="addStyle()">

Related

Can't delete all cards that were created by forEach

This forEach loop creates html elements (cards) when user click the button but if user click the button again all cards supposedly must be deleted. In my case when I click the button again only the first card is gone. I know that it smth has to do with id. I tried to do this () but I have no idea what to do next to delete all cards. thanks for attention
function getFood(){
if (foodBtn.classList.contains("yes")){
fetch("http://localhost:1314/getByType/food")
.then((resp => {
return resp.json();
}))
.then((resp) => {
resp.forEach(elem => {
div.innerHTML += `<div id="MyFood">
<div class="card" style="width: 18rem;">
<img src="${elem.image}" class="card-img-top" alt="...">
<div class="card-body">
<b>price: ${elem.price} $</b>
<p class="card-text">description: ${elem.description}</p>
<p class="card-text">amount: ${elem.amount}</p>
</div>
</div>
</div>`
})
})
foodBtn.classList.remove("yes");
foodBtn.classList.add("no");
}else {
const q = document.getElementById('MyFood');
console.log(q);
q.innerHTML = "";
foodBtn.classList.remove("no");
foodBtn.classList.add("yes");
}
}
You are indeed right. In html IDs are unique so using the same ID for multiple instances may not work as expected. The solution is either add something in the generation that would create unique IDs such as
resp.forEach((elem, index) => {
div.innerHTML += `<div id="MyFood${index}">
<div class="card" style="width: 18rem;">
<img src="${elem.image}" class="card-img-top" alt="...">
<div class="card-body">
<b>price: ${elem.price} $</b>
<p class="card-text">description: ${elem.description} </p>
<p class="card-text">amount: ${elem.amount}</p>
</div>
</div>
</div>`
})
or use a class instead of an ID (I would personally go with this)
resp.forEach(elem => {
div.innerHTML += `<div class="MyFood">
<div class="card" style="width: 18rem;">
<img src="${elem.image}" class="card-img-top" alt="...">
<div class="card-body">
<b>price: ${elem.price} $</b>
<p class="card-text">description: ${elem.description}</p>
<p class="card-text">amount: ${elem.amount}</p>
</div>
</div>
</div>`
})
Then to select and delete just call
const q = document.querySelectorAll('.MyFood');
for (let i = 0; i < q; i++) {
q[i].remove();
}
Multiple HTML elements may not share the same ID. That's why when you try to select #MyFood you're only getting one. Instead, you can change it to classes, which are sort of like IDs for multiple elements. In div.innerHTML += ..., change the first part of the string from <div id="MyFood" ... to <div class="MyFood" ..., then in your else you need to change the selector to classes:
const elements = [...document.getElementsByClassName("MyFood")]; // you can also do: [...document.querySelectorAll(".MyFood")]
elements.forEach(el => el.remove()); // loop through array of elements and remove each one
// ...
Learn more about the selector: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
It is certainly an id issue. If multiple HTML elements share the same id, when you call getElementById() only the first element with that id will get called. To fix this without changing much of your code, you instead of using id you can try setting class="MyFood" then call getElementsByClassName() method.
A different approach, if for some reason you need the id="MyFood" element to appear in your code, is to wrap all your cards inside a div like this:
<div id="MyFood">
...cards here
</div>
then the rest of your code will work without changing anything.

Adding div container with class name and html content with JavaScript

I'm looking for a way to add a div container using template literals. for example.
I have a div in my index.html
<div class="bag">
</div>
Every time the user adds a new item to the bag the following divs' get added inside the bag like so...
<div class="bag">
<div class="bag__item"> // <--- added here
<div class="wrapper--within">
<img src="./assets/images/cookie5-small.jpg" alt="" />
<h3 class="bag__unit-price">$5</h3>
<div class="bag__quantity-container">
<div class="bag__minus-sign"></div>
<h3 class="bag__quantity-container__quantity">2</h3>
<div class="bag__plus-sign-middle"></div>
<div class="bag__plus-sign-center"></div>
</div>
<div class="bag__total-price-container">
<img
class="bag__trash-icon"
src="./assets/images/trash-small.png"
alt=""
/>
<h2 class="bag__total-price">$10</h2>
</div>
</div>
</div> // <-- to here
</div>
In my javascript I target my bag container
class Cart {
constructor() {
this.cartContainer = document.querySelector(".bag");
this.events();
}
events() {
this.updateCart();
}
updateCart() {
let newItemDiv = document.createElement("div")
newItemDiv.className = "bag__item"
newItemDiv.createElement("div")
}
}
export default Cart;
I was originally planning to add each div individually but i would like a way where i can do something like..
updateCart() {
let newItemDiv = document.createElement("div")
add `<div class="bag__item"> // <--- added here
<div class="wrapper--within">
<img src="./assets/images/cookie5-small.jpg" alt="" /> // <---image will change depending on item added
<h3 class="bag__unit-price">$5</h3> // price will change depending on item added..
<div class="bag__quantity-container">
<div class="bag__minus-sign"></div>
<h3 class="bag__quantity-container__quantity">2</h3>
<div class="bag__plus-sign-middle"></div>
<div class="bag__plus-sign-center"></div>
</div>
<div class="bag__total-price-container">
<img
class="bag__trash-icon"
src="./assets/images/trash-small.png"
alt=""
/>
<h2 class="bag__total-price">$10</h2>
</div>
</div>
</div> `
}
Is this something that can be done?
In your updateCart() method you can write
updateCart() {
let newItemDiv = document.createElement("div")
newItemDiv.className = "bag__item"
newItemDiv.innerHTML = `your markup here with the whole div hierarchy`;
}
You can do this.
If you already added the div.bad
document.getElementsByClassName("bag").innerHTML = `<div> </div>`
or
var elemt = document.createElement("div")
elemt.innerHTML = `<div> </div>`
You can do it like this: (I implemented below example by Cart class because in your question you've been using Cart class to create new Item and I consider that using this class is mandatory, there are other ways to acheive below result with less lines of code)
class Cart {
constructor(img, price) {
this.img = img;
this.price = price;
this.cartContainer = document.querySelector('.bag');
this.events();
}
getTemplate() {
const template = `<div class="bag__item">
<div class="wrapper--within">
<img src="${this.img}" alt="" />
<h3 class="bag__unit-price">${this.price}</h3>
<div class="bag__quantity-container">
<div class="bag__minus-sign"></div>
<h3 class="bag__quantity-container__quantity">2</h3>
<div class="bag__plus-sign-middle"></div>
<div class="bag__plus-sign-center"></div>
</div>
<div class="bag__total-price-container">
<img
class="bag__trash-icon"
src="./assets/images/trash-small.png"
alt=""
/>
<h2 class="bag__total-price">$10</h2>
</div>
</div>
</div> `;
return template;
}
events() {
this.updateCart();
}
updateCart() {
const template = this.getTemplate();
let newItemDiv = document.createElement('div');
this.cartContainer.append(newItemDiv);
newItemDiv.outerHTML = template;
}
}
// sample img, price (You can set different values for img, price when you want to create new one, this static content is just for example)
const img = 'https://fakeimg.pl/350x200/000000/?text=Image1';
const price = '100$';
function addBag(){
new Cart(img, price);
}
<button onClick="addBag()">click me</button>
<div class="bag">ba continaer:</div>

Javascript reorder HTML element based on attribute value

I have the following HTML:
<div class="wrapper">
<div class="item" id="item-1">
</div>
<div class="item" id="item-2">
</div>
<div class="item" id="item-3">
</div>
</div>
And in javascript I'm currently applying filters & sort to the results of an array:
results = Object.keys(list).filter(.....);
results = results.sort((a, b) => (....) ? 1 : -1);
// After the results have been filtered & sorted, hide all HTML elements:
document.querySelectorAll('.item').forEach(i => i.classList.add('d-none'));
// And then proceed to show only the results that have been filtered & sorted:
results.forEach(index =>
{
let id = list[index].id;
let item = document.getElementById('item-' + id);
item.classList.remove('d-none');
});
This works great. The problem is that now I need to move the HTML elements according to the results array, specifically with the id field.
A) Expected output: Array ids [2, 1]
<div class="wrapper">
<div class="item" id="item-2">
</div>
<div class="item" id="item-1">
</div>
<div class="item d-none" id="item-3">
</div>
</div>
B) Expected output: Array ids [2]
<div class="wrapper">
<div class="item" id="item-2">
</div>
<div class="item d-none" id="item-1">
</div>
<div class="item d-none" id="item-3">
</div>
</div>
You can move elements to the top, using prepend function.
On bellow example, I only implement the logic about move elements, and I didn't protected against non existent elements.
You should add your logic's about filtering, etc. and protect for possible errors.
function sortDivs() {
let list = [2, 1];
let mainDiv = document.querySelectorAll('.wrapper')[0];
list.reverse().forEach(n => mainDiv.prepend(document.getElementById('item-'+n)));
}
.item {
border: black solid;
}
<div class="wrapper">
<div class="item" id="item-1">
item-1
</div>
<div class="item" id="item-2">
item-2
</div>
<div class="item" id="item-3">
item-3
</div>
</div>
<br>
<button onClick="sortDivs()"> sort DIVs</button>
One way would be to
iterate the result ids and get the correspondig object from the dom (combined with your last .forEach round removing the d-done class)
get all d-done elements and combine both lists
convert to html and reset innerHTML of the wrapper
let results = [2,1]
let wrapper = document.getElementById('wrapper-id')
wrapper.innerHTML = results.map(id => document.querySelector('#item-' + id))
.concat([...document.querySelectorAll('.d-none')])
.map(elem => elem.outerHTML)
.join('')
<div class="wrapper" id="wrapper-id">
<div class="item" id="item-1">
item-1
</div>
<div class="item" id="item-2">
item-2
</div>
<div class="item d-none" id="item-3">
item-3
</div>
</div>
Solved.
I had several problems:
The first one was instead of using Object.keys(results).filter I should be using results.filter, because I don't need to get only the Keys, which was making things way harder.
Secondly, the logic to apply in order to have everything re-organizing according to multiple filters is:
Sort / filter everything
Hide all items (using d-none)
Grab all the wrapper children const wrapperItems = wrapper.children
Create a variable named wrapperNewItems that holds the new sorted/filtered items
Create a variable that holds which ID's (item-1, item-2, etc) have been sorted/filtered
Push the items sorted into the variable and remove d-none
Push the items that were NOT sorted into the variable and keep d-none
Translated into code:
document.querySelectorAll('.item').forEach(i => i.classList.add('d-none'));
const wrapper = document.getElementsByClassName('wrapper')[0];
// Saves the existing children
const wrapperItems = wrapper.children;
// Holds the new items ordered perfectly
let wrapperNewItems = [];
// Holds names that were filtered (item-1, item-2, etc)
let listOfNamesFiltered = [];
// Shows only the items filtered
results.forEach(item =>
{
let name = 'item-' + item.id;
let el = document.getElementById(name);
el.classList.remove('d-none');
wrapperNewItems.push(el);
listOfNamesFiltered.push(name);
});
for (let i = 0; i < wrapperItems.length; i++)
{
let item = wrapperItems[i];
let name = item.id; // id="" already contains "item-{id}"
// If the name already exists in the list, we won't add it again
if (listOfNamesFiltered.includes(name))
continue;
wrapperNewItems.push(item);
}
// Clears the div
wrapper.innerHTML = '';
// Appends the items once again
for (let i = 0; i < wrapperNewItems.length; i++)
wrapper.innerHTML += wrapperNewItems[i].outerHTML;

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));
}

select multiple div inside for-loop for group add/delete functionality in angular4

I have a scenario in angular 4 environment,where i need to add/delete multiple members simultaneously,so inside a ngFor loop i have multiple divs but i want to select those div on which i click and for sure i'll click on multiple div at any given time.I could solve this only for one div meaning i could select only one div at any tine.
Please find the code snippets:
<div class="gp-row tomato-grower flexbox" *ngFor="let item of data.nonmembArray;let i=index">
<div class="pack flexbox" [ngClass]="{'active': clickedItem === i }" (click)=selectNonMember(i)>
<img src="url" alt="Tomato Grower" class="circle-with-border" height="64" width="64">
<div class="text">
<div class="name">{{item.profile.first_name +' '+ item.profile.last_name | slice:0:13}}</div>
<div class="city">Hyd</div>
<div class="phone">+91 7897897891</div>
</div>
</div>
Function implementation:
selectNonMember(i:number)
{
this.clickedItem =i;
// console.log(i);
}
And css::
.active
{
border: 3px solid grey;
}
Please resolve this issue.
You can avoid this problem by having the ngFor and the event binding in the same element, that way you can pass and show the class depending on where the user clicked
<div class="gp-row tomato-grower flexbox" >
<div class="pack flexbox"
*ngFor="let item of data.nonmembArray;let i=index"
[ngClass]="{'active': clickedItem === i }"
(click)="clickedItem = i">
<img src="url" alt="Tomato Grower" class="circle-with-border" height="64" width="64">
<div class="text">
<div class="name">{{item.profile.first_name +' '+ item.profile.last_name | slice:0:13}}</div>
<div class="city">Hyd</div>
<div class="phone">+91 7897897891</div>
</div>
</div>
Also you were missing some "" around the function called in (click)

Categories

Resources