fire function one time when element is created (pass to methods) / VueJS - javascript

I'm working with BootstrapVue. First I want to explain my code shortly:
I have a v-for with inputs, these I can create as often as I want with a b-button -> addElement. Or I can select an item from my b-dropdown - there I am checking the length of my selected and push the amount of my arrayLength as my inputs.
-> e.g. selected array = ['1111', '2222', '3333'] -> arrayLength = 3 -> 3 inputs
Now I need to get a function fired automatically (marked as ???) when exactly this case happend that I'm selecting something from my dropdown - e.g. is my arrayLength = 3, it should be fired 3 times with correct id, indexParent and indexChild. How can I solve that?
I need this above named parameters in my methods..
Thanks in advance!
<div v-for="(id, indexChild) in inputs" :key="indexChild">
<b-dropdown text="Select" right variant="success">
<b-dropdown-item v-for="(item, indexDropdown) in json" :key="indexDropdown" #click="trySomething(item.Number)">{{item.Name}}</b-dropdown-item>
</b-dropdown>
<b-button v-b-toggle="'newElement'+indexParent+indexChild">Element {{indexChild + 1}}></b-button>
<b-collapse :id="'newElement'+indexParent+indexChild"
<div ???="getValues(id, indexParent, indexChild)"> <!-- how can I fire this when created? -->
<!-- Do some more code -->
</div>
</b-collapse>
</div>
<!-- create new "item"-elements -->
<div>
<b-button #click="addElement()">Add element</b-button>
</div>
methods: {
addProduct() {
this.inputs.push({})
},
trySomething(itemValue) {
var array = [];
const products = this.otherJSON.find((i) => i.Number === itemValue);
for (let key in products.ID) {
array.push(products.ID[key]);
}
this.getLengthArray = array.length;
this.inputs.splice(0) //at first I need to delete all to push exact inputs based on arrayLength
for (let i = 0; i < this.getLengthArray; i++) {
this.inputs.push({});
}
},
getValues(id, indexParent, indexChild) { //Here I need the parameters id, indexParent, indexChild
}
},
data() {
return {
inputs: [{}],
}
},
props: ["indexParent"]

I've just used:
<div v-if="getValues(id, indexParent, indexChild)" v-once></div>
and it worked out for me!

Related

I have an issue with handlebars and a counter function in JavaScript

i'm trying create a counter function on object compilated with handlebars.js. I'm trying to make a project site for a moving company, and I'm trying to create a cost estimation system in it, depending on what things and how many they have to move in their new house
handlebars compilated divs
problem
So I created objects in js which contain things to move such as fridge, bed, table etc... their are compilated right into HTML with handlebars template.
I want to be able to increase and decrease the numbers of things separately, so I was able to create one function that does this, but the problem is that it works only on the first compilated object and all other objects are not affected with this code, i don't even know if it is possible to do such thing with handlebars template and vanilla js, I know I can do this for each objects individually but there will be way too much duplicated codes in HTML and JS files..
here is the handlebars template on HTML file:
<script src="https://cdn.jsdelivr.net/npm/handlebars#latest/dist/handlebars.js"></script>
<script id="templateHB" type="text/x-handlebars-template">
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button id="increase-{{this.index}}">+</button>
<p>{{this.quantity}}</p>
<button id="decrease-{{this.index}}">-</button>
</div>
</div>
{{/each}}
here is the js file code:
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
let compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// here start the function
let quantity = contextSalon.lesMeublesSalon[0].quantity;
let addOneBtn = document.getElementById("increase-0");
let removeOneBtn = document.getElementById("decrease-0");
function updateQuantity(quantity) {
contextSalon.lesMeublesSalon[0].quantity = quantity;
compiledHtmlSalon = template(contextSalon);
injectionObjetSalon.innerHTML = compiledHtmlSalon;
addOneBtn = document.getElementById("increase-0");
removeOneBtn = document.getElementById("decrease-0");
addOneBtn.addEventListener("click", function() {
updateQuantity(quantity + 1);
});
removeOneBtn.addEventListener("click", function() {
updateQuantity(quantity - 1);
});
}
updateQuantity(0);
if the thing i'm trying to do is impossible with js and handlebars.js, what other tech can you suggest me? any js framework such as node.js and express.js?
I just created function to increase and decrease number based on ID of the template inside html file with handlebars, I was expecting it to work with all others compilated objects to work the same way.
It is possible that a rendering framework like React or Vue could be used for this, but they come with learning curves. The conditions as you have outlined them are fairly simple and so I think you can get by with Handlebars.
But we will need to make some modifications to your code.
The first issue I see is that you have index: 0 set on all objects in your lesMeublesSalon array. You could correct those to be sequential (0, 1...), but I think a better option would be to use Handlebars' built-in #index variable to set the index value for each item.
The next problem I would address is that you are trying to re-render the Handlebars template with each click of the increment/decrement button. The problem I see with this is that it is that you will have to re-attach your event listeners each time you re-render the HTML because the DOM will have new button elements that need to be listened to.
I think a better approach would be to render the initial HTML with Handlebars, but then to use JavaScript to directly update the DOM when your counts change.
Here is how I would implement this:
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
const source = document.getElementById('templateHB').innerHTML;
const template = Handlebars.compile(source);
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
// Note the use of data-* attributes in the template.
// This allows us to query for ALL increment/decrement buttons
// and to attach a listener to each.
// Getting them by ID allowed us to get only one increment
// and one decrement button.
const addOneBtns = document.querySelectorAll("[data-increase]");
const removeOneBtns = document.querySelectorAll("[data-decrease]");
const quantityDisplays = document.querySelectorAll("[data-quantity]");
// This is the function that will directly manipulate the displayed
// quantity value for each item.
// It relies on the indexes of the elements to match the indexes in
// our lesMeublesSalon so that the correct quantity will be set.
function renderQuantities () {
quantityDisplays.forEach((quantityDisplay, index) => {
const quantity = contextSalon.lesMeublesSalon[index].quantity;
quantityDisplay.textContent = String(quantity);
})
};
// We loop through EACH increment button and attach a click listener.
addOneBtns.forEach(addOneBtn => {
addOneBtn.addEventListener('click', function (event) {
// We get the index from the `[data-increase]` attribute.
const index = Number(event.target.dataset.increase);
// We use that index to increment the quantity on the
// corresponding item in our array.
contextSalon.lesMeublesSalon[index].quantity += 1;
// We re-render the quantities because we know there is a change.
renderQuantities();
});
});
// This is basically the same as above, except for decrementing.
removeOneBtns.forEach(removeOneBtn => {
removeOneBtn.addEventListener('click', function (event) {
const index = Number(event.target.dataset.decrease);
// We use Math.max() so the quantity can't become less than zero.
contextSalon.lesMeublesSalon[index].quantity = Math.max(contextSalon.lesMeublesSalon[index].quantity - 1, 0);
renderQuantities();
});
});
I have created a Codepen for reference.
I've tesed your code and it works well , thanks you! however I forgot to mention that I have 4 categories of objects , as you can see on the picture , I got (Salon, Chambre, Cuisine, Bain) , how can we make so that it works on all of these categories ?
So it works on the "Salon" categories but not in the others,
here you have all others object :
const contextSalon = {
lesMeublesSalon: [
{
image: 'images/canape.png',
element: 'Canapé',
quantity: 0,
index: 0
},
{
image: 'images/canape.png',
element: 'Lit',
quantity: 0,
index: 0
}
]
};
// Initially render our HTML with Handlebars.
const compiledHtmlSalon = template(contextSalon);
const injectionObjetSalon = document.getElementById('meuble-salon');
injectionObjetSalon.innerHTML = compiledHtmlSalon;
const contextChambre = {
lesMeublesChambre: [
{
image: 'images/bed.svg.png',
element: 'Lit double',
quantity: 0,
index: 0
}
]
}
const compiledHtmlChambre = template(contextChambre);
const injectionObjetChambre = document.getElementById('meuble-chambre');
injectionObjetChambre.innerHTML = compiledHtmlChambre;
const contextCuisine = {
lesMeublesCuisine: [
{
image: 'images/frigo.svg',
element: 'Frigo',
quantity: 0,
index: 0
}
]
}
const compiledHtmlCuisine = template(contextCuisine);
const injectionObjetCuisine = document.getElementById('meuble-cuisine');
injectionObjetCuisine.innerHTML = compiledHtmlCuisine;
const contextBain = {
lesMeublesBain: [
{
image: 'images/machine-a-laver.svg',
element: 'Machine à laver',
quantity: 0,
index: 0
}
]
}
const compiledHtmlBain = template(contextBain);
const injectionObjetBain = document.getElementById('meuble-bain');
injectionObjetBain.innerHTML = compiledHtmlBain;
{{#each lesMeublesSalon}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button data-increase="{{#index}}">+</button>
<p data-quantity="{{#index}}">{{this.quantity}}</p>
<button data-decrease="{{#index}}">-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesChambre}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesCuisine}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}
{{#each lesMeublesBain}}
<div class="meuble"><img src="{{this.image}}">
<p>{{this.element}}</p>
<div class="plus-moin">
<button>+</button>
<p>{{this.quantity}}</p>
<button>-</button>
</div>
</div>
{{/each}}

while deleting the particular items from iteration of items in click event first item is deleting instead of clciked one

In my angular application I have some iteration items and saving the items based on adding the items.
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(index);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
}
public deletedata(index:number){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
this.selectedCategoryDetails.splice(index, 1);
}}
From the above code I have saved the data based on adding the items as above and my requirement is when we click on the delete(it will show the popup having the button yes implemented in anbove code).
when we click on the yes button from list of items, I have to remove the particular item
When I tried removing ,It is only deleting the first item instead of clicked one
Can anyone help me on the same
The logic for deletion is incorrect. The splice mutates the original array, and you are applying the loop for deletion, which keeps on iterating over the array and deleting the array elements based on index, instead of deleting single matched index element.
Example -
const categories = [
1,
2,
3,
4
];
function removal(i) {
categories.forEach((category, index) => {
categories.splice(i, 1);
});
console.log('----Categories-->', categories);
}
removal(0);
Categories Array
First Iteration [index = 0]
[1,2,3,4]
Loop Starts Iterating from 1
Second Iteration [index = 1]
[2,3,4]
Loop Starts Iterating from 3
Third Iteration [index = 2]
[3,4]
Stop
Instead you can use filter function.
public deletedata(index:number){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((_, i) => i! == index);
}
Note - I would recommend to delete the categories based on some identifier like id instead of index because the array elements position can get changed.
Instead of passing index to deleteData method, you can pass the category object.
public deletedata(category){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((c) => c.id! == category.id);
}

How do I delete a specific number in an Local Storage array?

I'm making a webapp with a grades calculator where I can Add grades and delete them. They are saved in a Local Storage. When trying to delete a specifig grade it deletes the grade recently added. How can I specifically delete an int in an Array?
<template>
<div>
<h2>Grade-Calculator</h2>
<div>
<ul>
<li v-for="(g,idx) in grades" :key="idx">
{{idx+1}}. Grade : {{g}}
<button v-on:click="delGrade()">Delete</button>
</li>
</ul>
<div>
<label>New Grade:</label>
<input type="text" v-model="newGrade" />
<button v-on:click="addGrade()">Add</button>
</div>
<br />
<div>
<p>Average: {{ calcAvg() | round}}</p>
</div>
</div>
</div>
</template>
<!-- The Script-->
<script>
export default {
data: () => ({
grades: [],
newGrade: null,
avg: 0,
formattedNumber: ""
}),
name: "Home",
props: {},
methods: {
delGrade: function() {
var idx = this.grades.indexOf(this.grades);
this.grades.splice(idx);
localStorage.removeItem("grades");
localStorage.setItem("grades", this.grades);
}
},
mounted() {
this.grades = JSON.parse(localStorage.getItem("grades")) || [];
},
};
</script>
First you need the grade to delete in delGrade(gradeToDelete) and once you have it (and its index by this.grades.indexOf(gradeToDelete)) you can work with the index.
Furthermore, I think .splice needs a deleteCount to remove the element at the index. Try changing this.grades.splice(idx); to this.grades.splice(idx, 1);
I'm assuming your data looks like integers in an array. In which they will be stored like the example below.
let grades = [8, 9, 5, 6, 3];
localStorage.setItem('grades', JSON.stringify(grades));
To remove a single key from the array, first parse it back to an array format and then use the Array.prototype.filter method create a new array without the grade that you want to remove.
let gradeToRemove = 5;
let storedGrades = JSON.parse(localStorage.getItem('grades'));
let newGrades = storedGrades.filter(grade => grade !== gradeToRemove);
localStorage.setItem('grades', JSON.stringify(newGrades));
You should provide id, which you want to delete from array
<button v-on:click="delGrade(idx)">Delete</button>
then you should receive that id and use it
delGrade: function(idToDelete) {
this.grades.splice(idToDelete, 1);
localStorage.removeItem("grades"); // it is not necessary, coz you will override all key grades in next line
localStorage.setItem("grades", this.grades);
}

How to do pop operation in javascript when unchecked checkbox?

I have just the two checkbox whose codes looks like:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(change)="editPartyRolesSubmit($event)"
/>
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
Here i have only used two checkbox as:
The function editPartyRolesSubmit($event) called is:
public documents: Array<Document> = []
public onlyTruedocuments: Array<Document> = [];
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
this.onlyTruedocuments.push(x);
console.log(x);
})
}
The Json data is pushed three times as it should be only pushed two times though i have just clicked two times.
But when i print them in the .html page then though the checkbox is two,It is printed three times :
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
It is printing like this:
How can i remove this redundant data?
If I understand correctly, I would do in this way,
I have used (ngModelChange) instead of (change)
I have passed the current item to the ngModelChange function.
HTML:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(ngModelChange)="editPartyRolesSubmit($event,item)"/> // will pass an item to the function
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
TS file:
export class YourComponent {
documents = [{
docId: 1,
checked: false,
docName: 'Prashant'
},
{
docId: 2,
checked: false,
docName: 'Venkat'
}
, {
docId: 2,
checked: false,
docName: 'Perry'
}];
public onlyTruedocuments: any = [];
editPartyRolesSubmit(event, obj) {
// Take the index of an Item checked
let index = this.onlyTruedocuments.indexOf(obj);
// Check for event i.e it is checked or unchecked
if (event) {
if (index == -1) {
// If the index is -1 then that means its not a duplicate so push into an array
this.onlyTruedocuments.push(obj);
}
}
else {
// If it is unchecked then we surely know that the item has to be removed from the array so by an index of the particular item we can [splice][1] the item
this.onlyTruedocuments.splice(index, 1)
}
}
}
No need to filter the source array to get the checked items.
A Working StackBlitz Example with Sample data.
Have a unique check before you push to onlyTruedocuments. This way even when user click many times the object would still have unique values as expected.
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
const exists = this.onlyTruedocuments.filter((f)=>f.docId == x.docId);
if(exists.length==0){
this.onlyTruedocuments.push(x);
}
console.log(x);
})
}
One way to collect unique elements only is to define this.onlyTruedocuments not as an array, but as a Set, initialised as:
this.onlyTrueDocuments = new Set();
Then in the event handler do:
editPartyRolesSubmit(event) {
this.documents.forEach(x => x.checked && this.onlyTruedocuments.add(x));
console.log([...this.onlyTrueDocuments]); // Use spread syntax to get converson to array
}

Angular/Javascript - Hide button with id onclick

I have multiple buttons on one page, "Add to cart" buttons where each button has a unique id attribute.
I want to hide a particular button when the user clicks on it.
The issue:
What's happening currently is that when a user clicks on a button 1 it hides, then clicks on button 2 it hides but on the same time it shows button 1
The expected behavior:
When the user clicks on button 1 it should hide and keep hiding even after clicking on button 2
P.S. the information of the buttons (products) gets added to an array.
Current code:
Html:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hideButton != i" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
JS
addToCart(itemDetails, index) {
this.hideButton = index;
}
You need an array of hidden buttons and you need to add the index to that array:
JS:
// at the top
hiddenButtons = [];
addToCart(itemDetails, index) {
this.hiddenButtons.push(index);
}
HTML:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hiddenButton.indexOf(i) === -1" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
If you have a cart to which products are being added, you can look in the cart to check whether the product already exists in it, and use that to decide whether to display the ADD button.
If your product objects can have more properties to them, you can do away with indexes completely.
HTML
<div *ngFor="let product of products">
<div *ngIf="productInCart(product)" [attr.id]="product.id" class="addButton" (click)="addToCart(product)">ADD</div>
</div>
JS
productInCart(product) {
return this.products.findIndex(p => p.id==product.id)!=-1;
}
addToCart(product) {
this.products.push(product);
}
<div *ngFor="let product of products; let i = index">
<div *ngIf="!product.isHidden" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
In component
addToCart(itemDetails, index) {
itemDetails.isHidden = true;
this.products[index] = itemDetails;
}
Logic behind this is to create a new property in product when it clicked for add to cart. Initially there will be no property with name isHidden. SO, it will return undefined and undefined will treat as false.
I would suggest the following:
<div *ngFor="let product of products; let i = index">
<div *ngIf="!isInCart(product)" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
private hiddenProducts = new Set<FooProduct>();
products: FooProduct[] = [];
loadProducts(){
this.products = // some value
hiddenProducts = new Set<FooProduct>();
}
isInCart(product: FooProduct): boolean {
return this.hiddenProducts.has(product);
}
addToCart(product: FooProduct, index: number){
// optional: check if the element is already added?
this.hiddenProducts.add(product);
// rest of your addToCart logic
}
Why using a set instead of a simple array?
Performance: access time is constant.
Why not use the index as identifier?
Weak against list mutations (filter, reorder, etc)

Categories

Resources