html code:(Here i added the html code to iterate the nested array which i want to get the value in edit page ) getMaterialListArray(i) => this method should iterate the array values when i click the select+ button.
<p>Sub Recipes</p>
<div formArrayName="recipeArray" *ngFor="let item of recipeArray.controls; let i = index;">
<div [formGroupName]="i" class="add-div" >
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Sub Recipe{{i+1}}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="add-div">
Material
<mat-list role="list" formcontrolName="material">
<mat-list-item role="listitem" *ngFor="let data of getMaterialListArray(i)">
<div class="file-name">
<span>{{data.title}}</span>
<div>{{item.wt_in_kgs_per_unit}}{{item.uom}}</div>
<div class="btn-custom" (click)="deleteMaterial(data, i)">Delete</div>
</div>
</mat-list-item>
</mat-list>
</div>
<div class="btn-custom" (click)="addMaterial(i)">select+</div>
<br><br>
<div class="add-div">
otherFormulation
<mat-list role="list">
<mat-list-item role="listitem" *ngFor="let form of getFormulationListArray(i)">
<div class="file-name">
<span>{{form.title}}</span>
<div>{{form.batch_qty}}{{form.unit_type}} </div>
<div class="btn-custom"(click)="deleteOtherFormulation(form, i)">Delete</div>
</div>
</mat-list-item>
</mat-list>
</div>
<div class="btn-custom" (click)="addOtherFormulation(i)">select+</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
component.ts:(commented lines are gives an error. please give me an solution to patch value from nested array) the pattern of an array)
List : [] = array[{
internalCode:Fo-3684,
title:biscuit,
refer_subrecipe:[{
material_id:[{ title:biscuit,
internalCode:Fo-3434
},
{
title:cake,
internalCode:Fo-3488
}]
}],
Refer_packaging:[{
material_id:[{
title:candywrapper,
uom:10kg
},
{
title:candypack,
uom:20kg
}]
}] like this....
this.formulationForm.patchValue({internalCode : result.internalCode,
myCode:result.myCode,
title : result.title,
unitsOfMeasurement:result.unit_type,
batchQuantity:result.batch_qty,
productType:result.product_type,
// recipeArray: {
// material:result.Refer_SubRecipe.material_id.title,
// otherformulations:result.Refer_SubRecipe.material_id.title
// },
// packageArray:{
// packMaterial:result.Refer_Packaging.material_id.title,
// UOM:result.Refer_Packaging.material_id.uom,
// weight:result.Refer_Packaging.material_id.wt_in_kgs_per_unit,
// Quantity:result.Refer_Packaging.material_id.quantity
// },
allergen:this.alergenList,
shelf_life:this.alergenList,
operational_instruction:result.ops_instructions,
version_no:result.version,
version_date:result.version_date,
approved_by:result.approved_by,
remarks: result.remarks });
}
})
this.recipeArray = this.formulationForm.get('recipeArray') as FormArray;
this.packageArray = this.formulationForm.get('packageArray') as FormArray;
}
Related
Consider the following code.
<template>
<div class="card-container">
<div class="row">
<div class="col s12">
<a #click="addCard()">Add Card</a>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cards" :key="index">
<div class="card-panel">
<span class="card-title">Card Title</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
cards: []
}
},
methods: {
addCard: function() {
this.cards.push({
description: "",
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
}
},
}
</script>
How to make the cards be grouped so that there are 4 rows and each row contains 4 cards? Upon reaching the fourth row the new cards go to the next page.
I thought I could use something like this codepen.io/parths267/pen/bXbWVv
But I have no idea how to get these cards organized in a pagination system.
The view would look something like this
My solution is calculate all cards of current page ahead.
Uses computed property to calculate the relate values which the pagination needs.
In below simple example (it is only one example, you need to add necessary validations as your actual needs, like boundary conditions) :
pages is the page count
cardsOfCurPage is the cards in current page
Then add one data property=pageIndex save the index of current page.
Anyway, keep data-driven in your mind.
List all arguments your pagination needs,
then declare them in data property or computed property.
execute the necessary calculations in computed property or methods.
PS: I don't know which css framework you uses, so I uses bootstrap instead.
Vue.component('v-cards', {
template: `<div class="card-container">
<div class="row">
<div class="col-12">
<a class="btn btn-danger" #click="addCard()">Add Card</a><span>Total Pages: {{pages}} Total Cards: {{cards.length}} Page Size:<input v-model="pageSize" placeholder="Page Size"></span>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cardsOfCurrPage" :key="index" class="col-3">
<div class="card-panel">
<span class="card-title">Card Title: {{card.description}}</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
<p><a class="badge" #click="gotoPrev()">Prev</a>- {{pageIndex + 1}} -<a class="badge" #click="gotoNext()">Next</a></p>
</div>`,
data: function() {
return {
cards: [],
pageSize: 6,
pageIndex: 0
}
},
computed: {
pages: function () {
return Math.floor(this.cards.length / this.pageSize) + 1
},
cardsOfCurrPage: function () {
return this.cards.slice(this.pageSize * this.pageIndex, this.pageSize * (this.pageIndex+1))
}
},
methods: {
addCard: function() {
this.cards.push({
description: this.cards.length,
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
},
gotoPrev: function() {this.pageIndex -=1},
gotoNext: function() {this.pageIndex +=1}
},
})
new Vue({
el: '#app',
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div id="app">
<v-cards></v-cards>
</div>
i want to bind my button only on the element that i added to the cart, it's working well when i'm not in a loop but in a loop anything happen. i'm not sure if it was the right way to add the index like that in order to bind only the item clicked, if i don't put the index every button on the loop are binded and that's not what i want in my case.
:loading="isLoading[index]"
here the vue :
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading[index]"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here the data :
data () {
return {
products : [],
isLoading: false,
}
},
here my add to cart method where i change the state of isLoading :
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
You can change your isLoading to an array of booleans, and your addToCart method to also have an index argument.
Data:
return {
// ...
isLoading: []
}
Methods:
addToCart(product, index) {
// ...
}
And on your button, also include index:
#click="addToCart(product, index)"
By changing isLoading to an empty array, I don't think isLoading[index] = true will be reactive since index on isLoading doesn't exist yet. So you would use Vue.set in your addToCart(product, index) such as:
this.$set(this.isLoading, index, true)
This will ensure that changes being made to isLoading will be reactive.
Hope this works for you.
add on data productsLoading: []
on add to cart click, add loop index to productsLoading.
this.productsLoading.push(index)
after http request done, remove index from productsLoading.
this.productsLoading.splice(this.productoading.indexOf(index), 1)
and check button with :loading=productsLoading.includes(index)
You can create another component only for product card,
for better option as show below
Kindly follow this steps.
place the content of card in another vue component as shown below.
<!-- Product.vue -->
<template>
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="" alt="Placeholder image">
</figure>
</div>
<div class="card-content">
<div class="content">
<div class="media-content">
<p class="title is-4">{{product.name}}</p>
<p class="subtitle is-6">Description</p>
<p>{{product.price}}</p>
</div>
</div>
<div class="content">
<b-button class="is-primary" #click="addToCart(product)" :loading="isLoading"><i class="fas fa-shopping-cart"></i> Ajouter au panier</b-button>
</div>
</div>
</div>
</templete>
<script>
export default {
name: "Product",
data() {
return {
isLoading: false
}
},
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart(product) {
this.isLoading = true
axios.post('cart/add-to-cart/', {
data: product,
}).then(r => {
this.isLoading = false
}).catch(e => {
this.isLoading = false
});
}
}
}
</script>
Change your component content as shown below.
<template>
<div class="container column is-9">
<div class="section">
<div class="columns is-multiline">
<div class="column is-3" v-for="(product, index) in computedProducts">
<product :product="product" />
</div>
</div>
</div>
</div>
</templete>
<script>
import Product from 'path to above component'
export default {
components: {
Product
}
}
</script>
so in the above method you can reuse the component in other components as well.
Happy coding :-)
I have an input-component which has a form which collects start and finish times, job number and a select option.
This is attached to a data property with v-model.
This is then emitted with Event.$emit('addedData', this.hours)
In the display-component the Event.$on takes this data and checks an attribute and based on the check adds it to another data property (array) with this.todays_hours.push().
The template then displays this reactively using v-for in the template.
To this point all works fine. However when I then attempt to add another line of hours the hours already displayed change reactively with the input.
As my input-component also posts to a database with axios if I reload the page all is displayed correctly.
input-component
<template>
<div>
<div class="row">
<div class="col-2">
<input hidden="" v-model="hours.day">
</div>
<div class="col-2" >
<input type="time" v-model="hours.start">
</div>
<div class="col-2" >
<input type="time" v-model="hours.finish">
</div>
<div class="col-2" >
<input type="number" v-model="hours.job_number">
</div>
<div class="col-2" >
<select v-model="hours.climbing">
<option selected="selected" value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
<div class="col-2" >
<button #click="onSave" class="btn-success btn-sm">Save</button>
</div>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'InputHoursComponent',
props: ['employeeId', 'dayCheck', 'weekEnding'],
data() {
return {
hours: {
start: "",
finish: "",
job_number: "",
climbing: 0,
day: this.dayCheck
},
climbing_select: ['No', 'Yes'],
}
},
methods: {
onSave()
{
axios.post('/payroll', {
employee_id: this.employeeId,
week_ending: this.weekEnding,
start: this.hours.start,
finish: this.hours.finish,
job_number: this.hours.job_number,
climbing: this.hours.climbing,
day: this.dayCheck
})
.then(response => {})
.catch(e => {this.errors.push(e)});
let data = this.normalizeProp(this.hours, s, true)
Event.$emit('onAddedEntry', data);
console.log("passed data:", this.hours);
}
}
}
</script>
display-component
<template>
<div>
<div v-for="item in todays_hours">
<div class="row">
<div class="col-2">
<div hidden="" ></div>
</div>
<div class="col-2" >
<div v-text="item.start"></div>
</div>
<div class="col-2" >
<div v-text="item.finish"></div>
</div>
<div class="col-2" >
<div v-text="item.job_number"></div>
</div>
<div class="col-2" >
<div v-text="(item.climbing)?'Yes':'No'"></div>
</div>
<div class="col-2" >
<button #click="onEdit" class="btn-warning btn-sm mb-1">Edit</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DisplayHoursComponent',
props: ['dayCheck', 'hoursWorked'],
data() {
return {
hours_list: this.hoursWorked,
todays_hours: []
}
},
mounted() {
for (var i = 0; i < this.hours_list.length; i++) {
if (this.hours_list[i].day === this.dayCheck) {
this.todays_hours.push(this.hours_list[i])
}
}
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push(check);
}
})
},
methods: {
onEdit()
{
}
}
}
</script>
Can someone please help me?
Try pushing a copy of check instead of check itself.
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push({...check});
}
})
You could also make the copy when you emit the event instead.
I have just started experimenting with vue js and I am building a checkout form with it. I am also using Symfony 31 for the project. On the checkout/signup page I have an embedded collection of forms representing order items (each are subscription to a type of product). You can select multiple items by ticking a checkbox. You can also change the quantity. Unfortunately I cannot manage to pass the quantity update to the Vue instance. The entries are registered on render with the quantity 1, and if I change the quantity and then select the item, the price is calculated correctly, but the app registers this as a new entity. The binding with the quantity is not working. I will also need to add a similar field called frequency and I know I will have the same problem. Help?
Here is the js fiddle: https://jsfiddle.net/wavsu8xm/
Javascript:
var bus = new Vue();
var entriesComponent = Vue.component('entries', {
template: '#entries',
props: {
entries: [Array, Object],
selected: Array,
addons: Array,
frequencies: [Array, Object],
},
watch: {
selected: function(val, oldVal) {
bus.$emit('selected-changed', val);
},
}
});
new Vue({
el: '#app',
data: {
entries: [],
selected: [],
addons: [],
frequencies: [],
paymentConfig: {
advance: 25,
firstweek: 25,
ondelivery: 50,
},
weeks: 12,
},
components: {
'entriesComponent': entriesComponent,
},
created: function() {
// store this to use with Vue.set
var temp = this;
bus.$on('selected-changed', function(selected) {
// vm.$set deprecated
Vue.set(temp, 'selected', selected);
});
},
computed: {
totalAdvance: function() {
return (this.paymentConfig.advance * this.total) / 100;
},
totalFirstWeek: {
get: function() {
return (this.paymentConfig.firstweek * this.total) / 100;
},
},
onDeliveryPayment: {
get: function() {
return (this.paymentConfig.ondelivery * this.total) / (this.weeks * 100);
}
},
total: {
get: function() {
var sum = 0;
var weeks = this.weeks;
this.selected.forEach(function(item) {
sum += weeks * item.itemPrice * item.quantity;
});
console.log(sum);
return sum;
}
}
}
});
Template:
<section class="content">
<div class="row" id="app">
<div class="col-md-8">
<div class="box box-primary">
<div class="box-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="control-label required">Items</label>
<div class="col-md-12">
<entries :entries="{ 0 : { shareSize : 'Small', quantity : '1', itemPrice : '24', frequency : '' }, 1 : { shareSize : 'Medium', quantity : '1', itemPrice : '35', frequency : '' }, 2 : { shareSize : 'Large', quantity : '1', itemPrice : '46', frequency : '' } }"
:selected="selected"></entries>
<!-- component template -->
<template id="entries">
<div class="col-md-12">
<div class="form-group" v-for="(entry, key) in entries" v-bind:entry="entry">
<div class="form-group col-md-12">
<div class="col-md-12">
<div class="col-md-4">
<input type="checkbox" v-bind:value="entry" v-model="selected">
</div>
<div class="col-md-4">{{entry.shareSize}}</div>
<div class="col-md-4">{{'$ ' + Number(entry.itemPrice).toFixed(2) }}</div>
</div>
<div class="form-group col-md-12">
<div class="col-md-6">
<input type="number" v-model="entry.quantity" :value="entry.quantity" />
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="box box-info">
<div class="box-body" style="padding:15px;">
<div class="container-fluid">
<div class="form-group">
<div class="control-label">
<label>Summary</label>
</div>
<div class="form-control" v-for="item in selected">
<span class="pull-left small-box-footer">{{ item.shareSize }}</span>
<span class="pull-right">{{ item.quantity + ' x $ ' + (item.itemPrice*item.quantity).toFixed(2)}}</span>
</div>
<div class="control-label">
<label>Payment plan</label>
</div>
<div class="col-md-12">
{{ '$ ' + totalAdvance.toFixed(2) }} - advance
</div>
<div class="col-md-12">
{{ '$ ' + totalFirstWeek.toFixed(2) }} - first week
</div>
<div class="col-md-12">
{{ '$ ' + onDeliveryPayment.toFixed(2) }}/ week on each of the {{ weeks }} weeks of the subscription
</div>
<div class="col-md-12 row">
<div class="control-label"><strong><span class="pull-left">Total</span><span class="pull-right">{{ '$ ' + total.toFixed(2) }}</span></strong></div>
</div>
<div class="col-md-12 row">
<div class="title"><strong><span class="pull-left">Total due now</span><span class="pull-right">{{ '$ ' + totalAdvance.toFixed(2) }}</span></strong></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
You are mutating your props directly which will get overwritten when the parent component re-renders, so you need to make a copy of them inside your component, which can be done inside the created method of your component:
created: function() {
// copy props to data
this.entriesCopy = this.entries;
this.selectedCopy = this.selected;
},
data: function() {
return{
entriesCopy: [],
selectedCopy: []
}
}
Now you just need to update your watcher:
watch: {
selectedCopy: function(val, oldVal) {
bus.$emit('selected-changed', val);
}
}
And your template:
//...
div class="form-group" v-for="(entry, key) in entriesCopy" v-bind:entry="entry">
//...
<input type="checkbox" v-bind:value="entry" v-model="selectedCopy">
to reflect the changes.
Here's the updated jsfiddle: https://jsfiddle.net/5pyw74h9/
I'm using Vue.js v2 and I've defined a single-file component, RegionFacet.vue. It lists some regions that relate to polygons on a map (click a value, the corresponding polygon appears on the map).
Separately, I have a reset button. When that gets clicked, I call a method in RegionFacet to unselect any checkboxes displayed by RegionFacet. The model does get updated, however, the checkboxes remain checked. What am I missing?
<template>
<div class="facet">
<div class="">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<a data-toggle="collapse"v-bind:href="'#facet-' + this.id"><h4 class="panel-title">Regions</h4></a>
</div>
<div v-bind:id="'facet-' + id" class="panel-collapse collapse in">
<ul class="list-group">
<li v-for="feature in content.features" class="list-group-item">
<label>
<input type="checkbox" class="rChecker"
v-on:click="selectRegion"
v-bind:value="feature.properties.name"
v-model="selected"
/>
<span>{{feature.properties.name}}</span>
</label>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['content'],
data: function() {
return {
id: -1,
selected: []
}
},
methods: {
selectRegion: function(event) {
console.log('click: ' + event.target.checked);
if (event.target.checked) {
this.selected.push(event.target.value);
} else {
var index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
this.$emit('selection', event.target.value, event.target.checked);
},
reset: function() {
this.selected.length = 0;
}
},
created: function() {
this.id = this._uid
}
}
</script>
<style>
</style>
You are directly setting the array length to be zero, which cannot be detected by Vue, as explained here: https://v2.vuejs.org/v2/guide/list.html#Caveats
Some more info: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
To overcome this, you may instead set the value of this.selected as follows:
reset: function() {
this.selected = [];
}