how to combine two table values to one array in vuetify - javascript

I have a vuetify table with multiple values consisting of, Date,start and endtime and votername. What I am trying know is to push on button click two selected rows into one new array object. I can push already the selected rows to one array but each of them being seperate object inside my array but this is not what I want.
this is what I am getting if I push the whole array:
{
"id":null,
"date":"2021-06-18",
"startTime":"23:00",
"endTime":"23:30",
"voterUniqueName":"Hasan",
"meetingName":"hallo"
},
{
"id":null,
"date":"2021-06-18",
"startTime":"23:00",
"endTime":"23:30",
"voterUniqueName":"Turan",
"meetingName":"hallo"
}
but what I want to get is the following:
{
"voterUniqueName": "Hasan",
"voterUniqueName2": "Turan"
},
I want to simplify the two values like this two one array object.
but when I try to push the names of the array I am getting an undefined. Could someone look at my code and tell me where my mistake is?
HTML:
<v-data-table
v-model="selected"
:headers="headers"
:items="filterByDate"
item-key="voterUniqueName"
show-select
class="elevation-1"
>
<template v-slot:top>
<v-col cols="3">
<v-select
:items="availableTimes"
item-text="date"
#select="filterByDate"
v-model="selectedDate"
></v-select>
</v-col>
<v-col cols="12">
<v-chip-group
v-model="selection"
active-class="deep-purple--text text--accent-4">
<v-chip
v-for="(time, i) in dateTimeArray"
:key="time"
:value="time"
#click="getTimesFiltered(time)">
{{ time.startTime +" : "+ time.endTime }}
</v-chip>
</v-chip-group>
</v-col>
</template>
</v-data-table>
script:
<script>
import axios from "axios";
export default {
name: "MeetingAdminComponent",
data : ()=>({
singleSelect: false,
selected: [],
meetingArray: [],
headers: [
{
text: 'Date',
align: 'center',
value: 'date',
},
{ text: 'Voter', value: 'voterUniqueName' },
{ text: 'Starttime', value: 'startTime' },
{ text: 'Endtime', value: 'endTime' },
],
availableTimes: [
],
}),
methods:{
pushSelected(){
this.meetingArray.push({
name1: this.selected.name,
name2: this.selected.name
})
console.log(this.meetingArray)
}
}
};
</script>

If all you wanna achieve is something like this:
{
"voterUniqueName": "Hasan",
"voterUniqueName2": "Turan"
}
Then simply do something like this:
// In your case this will be the selected items: this.selected
const voters = [
{
"id":null,
"date":"2021-06-18",
"startTime":"23:00",
"endTime":"23:30",
"voterUniqueName":"Hasan",
"meetingName":"hallo"
},
{
"id":null,
"date":"2021-06-18",
"startTime":"23:00",
"endTime":"23:30",
"voterUniqueName":"Turan",
"meetingName":"hallo"
}
]
// Create an empty object to join the items
const joinedVoters = {};
voters.forEach((voter, index) => {
// Gets the voter position plus one for the key
const item = index + 1;
// This will insert the "voterUniqueName in the position 0 of the array as the key "name1"
joinedVoters[`name${item}`] = voter.voterUniqueName;
})
console.log(joinedVoters)
For your specific case, your method should end up looking this:
methods:{
pushSelected(){
const newMeeting = {};
this.selected.forEach((voter, i) => {
const key = `name${i + 1}`;
newMeeting[key] = voter.voterUniqueName;
})
this.meetingArray.push(newMeeting);
console.log(this.meetingArray)
}
}

Just a question I noticed that my structure is completely wrong what I asked in my question. Should I post a new question and mark this one as closed or should I just update my question?

Related

Vuetify Table - rendering column value based on a condition

I have a vuetify table and one of the columns is url. I need to add logic to either display URL or URL Group name. I can tell that based on the property of my rules array. If my rules[i].urlGroup != '' then I know
I have tried to add
<template v-slot:item.url="{ index, item }">
{{ displayURLInfo(index) }}
</template>
displayURLInfo(index) {
if (this.rules[index].urlGroup != '') {
// console.log('this.rules[index].urlGroup', this.rules[index].urlGroup) // here printed perfectly on console.
return this.rules[index].urlGroup
} else {
return this.url
}
}
I was inside the first if condition, it consoles log perfectly, but it didn't render to the UI.
My rules array structure look like this. It's only has 5 properties
What did I do wrong?
You have to do below correction in the displayURLInfo() method :
Use urlGroup instead of userGroup.
Instead of return this.url it should be return this.rules[index].url
Working Demo :
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{ text: "Priority", value: 'priority' },
{ text: "Name", value: 'name' },
{ text: "URL", value: 'url' },
],
rules: [{
priority: 1,
name: "Alpha",
url: 'https://example.com',
urlGroup: ''
}, {
priority: 2,
name: "Beta",
url: 'https://example.com',
urlGroup: 'userGroup2'
}],
}
},
methods: {
displayURLInfo(index) {
if (this.rules[index].urlGroup != '') {
return this.rules[index].urlGroup
} else {
return this.rules[index].url
}
}
}
})
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.4/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.4/dist/vuetify.min.css"/>
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="rules"
>
<template v-slot:item.url="{ index, item }">
{{ displayURLInfo(index) }}
</template>
</v-data-table>
</v-app>
</div>

How to have parent data be updated by child component with multiple values

Code below.
I think I'm missing a crucial piece here. I've been through the docs and watched the entire vue2 step by step. Everything is making sense so far but I'm stuck on what seems to be a core piece. Any help would be appreciated. If this is totally wrong, please let me know, I'm not married to any of this stuff.
Desired functionality: There is an order Vue instance and it has line items.
On order.mounted() we hit an api endpoint for the order's data, including possible existing line items. If there are existing line items, we set that order data (this.lineitems = request.body.lineitems or similar). This part works fine and I can get the order total since the orders' line items are up to date at this point.
Each line item is an editable form with a quantity and a product . If I change the quantity or product of any line item, I want the child line-item component to notify the parent component that it changed, then the parent will update its own lineitems data array with the new value, and preform a POST request with all current line item data so the server side can calculate the new line item totals (many specials, discounts, etc). This will return a full replacement array for the order's line item data, which in turn would passed down to the line items to re-render.
Problems:
The line-items components "update..." methods are feeling obviously wrong, but my biggest issue is understanding how to get the parent to update its own line items data array with the new data. for instance
​
lineitems = [
{id: 1000, quantity: 3, product: 555, total: 30.00},
{id: 1001, quantity: 2, product: 777, total: 10.00}
]
If the second line item is changed to quantity 1, how do I get the parent's lineitems data to change to this? My main problem is that I don't know how the parent is suppose to know which of its own lineitems data array need to be modified, and how to grab the data from the changed child. I assume it came in via an event, via emit, but do I now need to pass around the primary key everywhere so I can do loops and compare? What if its a new line item and there is no primary key yet?
Mentioned above, I'm using the existing line item's DB primary key as the v-for key. What if I need a "new lineitem" that appends a blank lineitem below the existing ones, or if its a new order with no primary keys. How is this normally handled.
Is there a best practice to use for props instead of my "initial..." style? I assume just using $emit directly on the v-on, but I'm not sure how to get the relevant information to get passed that way.
This seems like the exact task that VueJS is suited for and I just feel like I keep chasing my tail in the wrong direction. Thanks for the help!
LineItem
Vue.component('line-item', {
props: ["initialQuantity", "initialProduct", "total"],
data () {
return {
// There are more but limiting for example
quantity: initialQuantity,
product: initialProduct,
productOptions = [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
updateQuantity(event) {
item = {
quantity: event.target.value,
product: this.product
}
this.$emit('update-item', item)
},
updateProduct(event) {
item = {
quantity: this.quantity,
product: event.target.value
}
this.$emit('update-item', item)
}
template: `
<input :value="quantity" type="number" #input="updateQuantity">
<select :value="product" #input="updateProduct">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ total }}
<hr />
`
})
Order/App
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
this.fetchLineItems()
},
computed: {
total() {
// This should sum the line items, like (li.total for li in this.lineitems)
return 0.0
},
methods: {
updateOrder(item) {
// First, somehow update this.lineitems with the passed in item, then
fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
fetchLineItems() {
fetch(`domain.com/api/orders/${this.orderPK}`)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
},
template: `
<div>
<h2 id="total">Order total: {{ total }}</h2>
<line-item v-for="item in lineitems"
#update-item="updateOrder"
:key="item.id"
:quantity="item.quantity"
:product="item.product"
:total="item.total"
></line-item>
</div>
`
})
Here's a list of problems in your attempt that would prevent it from displaying anything at all i.e.
quantity: initialQuantity, - surely you meant quantity: this.initialQuantity, ... etc for all the other such data
missing } for computed total
your line-item template is invalid - you have multiple "root" elements
And then there's some minor issues:
you want the #change handler for the select, not #input, if your code ran, you'd see the difference,
Similarly you want #change for input otherwise you'll be making fetch requests to change the items every keystroke, probably not what you'd want
So, despite all that, I've produced some working code that does all you need - mainly for my own "learning" though, to be fair :p
// ******** some dummy data and functions to emulate fetches
const products = [
{ id: 333, text: "Product A", unitPrice: 10},
{ id: 555, text: "Product B", unitPrice: 11},
{ id: 777, text: "Product C", unitPrice: 12},
];
let dummy = [
{id: 1, quantity:2, product: 333, total: 20},
{id: 2, quantity:3, product: 777, total: 36},
];
const getLineItems = () => new Promise(resolve => setTimeout(resolve, 1000, JSON.stringify({lineitems: dummy})));
const update = items => {
return new Promise(resolve => setTimeout(() => {
dummy = JSON.parse(items);
dummy.forEach(item =>
item.total = parseFloat(
(
item.quantity *
(products.find(p => p.id === item.product) || {unitPrice: 0}).unitPrice *
(item.quantity > 4 ? 0.9 : 1.0)
).toFixed(2)
)
);
let res = JSON.stringify({lineitems: dummy});
resolve(res);
}, 50));
}
//********* lineItem component
Vue.component('line-item', {
props: ["value"],
data () {
return {
productOptions: [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
methods: {
doupdate() {
this.$emit('update-item', this.value.product);
}
},
template: `
<p>
<input v-model="value.quantity" type="number" #change="doupdate()"/>
<select v-model="value.product" #change="doupdate()">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ '$' + value.total.toFixed(2) }}
</p>
`
})
//********* Order/App
const orderPK = '';
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
// initial load
this.fetchLineItems();
},
computed: {
carttotal() {
return this.lineitems.reduce((a, {total}) => a + total, 0)
}
},
methods: {
updateOrder(productCode) {
// only call update if the updated item has a product code
if (productCode) {
// real code would be
// fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems).then(resp => resp.json())
// dummy code is
update(JSON.stringify(this.lineitems)).then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
}
},
fetchLineItems() {
// real code would be
//fetch(`domain.com/api/orders/${this.orderPK}`).then(resp => resp.json())
// dummy code is
getLineItems().then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
},
addLine() {
this.lineitems.push({
id: Math.max([this.lineitems.map(({id}) => id)]) + 1,
quantity:0,
product: 0,
total: 0
});
}
},
template: `
<div>
<h2 id="total">Order: {{lineitems.length}} items, total: {{'$'+carttotal.toFixed(2)}}</h2>
<line-item v-for="(item, index) in lineitems"
:key="item.id"
v-model="lineitems[index]"
#update-item="updateOrder"
/>
<button #click="addLine()">
Add item
</button>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
</div>
note: there may be some inefficient code in there, please don't judge too harshly, I've only been using vuejs for a week

Vue : Store dynamic number of inputs in an dynamic two dimensional array

I get an multi dimensional array named response from back-end and what I'm trying to do is to create several text-fields depending on the number of element I have in response (each response element has some inner elements like response[0][0] and response[0][1] that each of them is a object contains caption,name,etc for each text-field. for example response[0][0].name get name of response[0][0] element).
What I want is to bind these text-fields to an other two dimensional array named data so I can get value of them and use them as I want.
Here's the code:
<v-layout row wrap v-for="(row,i) in response" :key = "i">
<v-layout v-for="(col,j) in row" :key = "j">
<v-text-field
:name = "col.name"
:label = "col.caption"
v-model="data[i][j]"//I think somehow i should create data[i][j] element
first,like data[i] =[]
>
</v-text-field>
</v-layout>
</v-layout>
And script is :
data () {
return {
data: [],
response: []
}
},
mounted: function () {
//get response from back-end
}
I'm new to Vue and javascript, any help would be appreciate...
please comment If it's not clear.
should work... can you clarify what the issue is? is it matter of generating the response array?
new Vue({
el: '#app',
data: {
data: [
[{
name: 'name00',
caption: 'caption00'
},
{
name: 'name01',
caption: 'caption01'
}
],
[{
name: 'name10',
caption: 'caption10'
},
{
name: 'name11',
caption: 'caption11'
}
]
],
response: [
['',''],
['','']
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.5/vue.min.js"></script>
<div id="app">
<div v-for="(i, ii) in data" :key="ii">
<div v-for="(j, jj) in i" :key="jj">
{{j.name}}
<input v-model="response[ii][jj]" />
</div>
</div>
<pre>{{response}}</pre>
</div>

vue.js computed property not triggered

Vue JS computed property is not triggered With this markup
<!-- language: lang-html -->
<p>£{{plant_price}}</p>
<div v-if="selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
<!-- language: lang-js -->
var app = new Vue({
el: '#vueApp',
data: {
selected: {
type: {a: '' , b: ''},
vehicle: '',
plant: {
}
},
computed: {
plant_price: function() {
if (this.selected.plant.variations.length > 0 ) {
var variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
...
selected.plant is populated by clicking on a plant - triggering the updateSelected method.
<div class="col-sm-4" v-for="(plant, i) in step2.plants">
<div v-on:click="updateSelected(plant)" ....
methods: {
updateSelected: function(plant) {
this.selected.plant = plant; // selected plant
if (this.selected.plant.variations.length > 0 ) {
this.selected.plant.selected_variation = this.selected.plant.variations[0].id; // set the selected ID to the 1st variation
I have checked through the debugger, and can see that all the correct properties are available.
selected:Object
type:Object
vehicle: "Truck"
plant:Object
id:26
price:"52"
regular_price:"100"
selected_variation:421
variations:Array[2]
0:Object
id:420
name:"small"
price:52000
regular_price:52000
1:Object
etc...
I have a computed property, which should update the plant_price based on the value of selected.plant.selected_variation.
I grab selected.plant.selected_variation and search through the variations to retrieve the price. If no variation exists, then the plant price is given.
I have a method on each product to update the selected plant. Clicking the product populates the selected.plant and triggers the computed plant_price to update the price (as the value of selected.plant.selected_variation has changed).
However, the computed plant_price is not triggered by the select. Selecting a new variant does what its supposed to, it updates selected.plant.selected_variation. Yet my plant_price doesn't seem to be triggered by it.
So I refactored my code by un-nesting selected.plant.selected_variation. I now hang it off the data object as
data = {
selected_variation: ''
}
and alter my computer property to reference the data as this.selected_variation. My computed property now works??? This makes no sense to me?
selected.plant.selected_variation isn't reactive and VM doesn't see any changes you make to it, because you set it after the VM is already created.
You can make it reactive with Vue.set()
When your AJAX is finished, call
Vue.set(selected, 'plant', {Plant Object})
There're two ways you can do it, what you are dealing with is a nested object, so if you want to notify the changes of selected to the others you have to use
this.$set(this.selected, 'plant', 'AJAX_RESULT')
In the snippet I used a setTimeout in the created method to simulate the Ajax call.
Another way you can do it is instead of making plant_price as a computed property, you can watch the changes of the nested properties
of selected in the watcher, and then update plant_price in the handler, you can check out plant_price_from_watch in the snippet.
Vue.component('v-select', VueSelect.VueSelect);
const app = new Vue({
el: '#app',
data: {
plant_price_from_watch: 'not available',
selected: {
type: {a: '' , b: ''},
vehicle: "Truck"
}
},
computed: {
plant_price() {
return this.setPlantPrice();
}
},
watch: {
selected: {
handler() {
console.log('changed');
this.plant_price_from_watch = this.setPlantPrice();
},
deep: true
}
},
created() {
setTimeout(() => {
this.$set(this.selected, 'plant', {
id: 26,
price: '52',
regular_price: '100',
selected_variation: 421,
variations: [
{
id: 420,
name: "small",
price: 52000,
regular_price: 52000
},
{
id: 421,
name: "smallvvsvsfv",
price: 22000,
regular_price: 22000
}
]
})
}, 3000);
},
methods: {
setPlantPrice() {
if (!this.selected.plant) {
return 'not available'
}
if (this.selected.plant.variations.length > 0 ) {
const variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<p>£{{plant_price}}</p>
<p>£{{plant_price_from_watch}}</p>
<div v-if="selected.plant && selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
</div>

Reading checkbox values from vue.js component

A few days ago I started using vue.js and trying to get the hang of it.
I've been fiddling quite a bit to get this easy example to work: reading the value of selected checkboxes in components with vue.js .
Please see my example on http://jsbin.com/gukoqo/edit?html,js,output
How can I let selected in the parent instance contain the selected values of the checkbox? E.g., filter_a and filter_c are selected, then selected should contain an array: ['filter_a', 'filter_c']
I expected vue.js to make this very easy, but don't know yet how to. Anyone? :)
I'm using the latest vue.js (2.3.3 at the moment)
One possible way.
Vue.component('facet-filter', {
props: ['filter', 'checked'],
template: `<div>
<label class="form-check-label">
<input #change="$emit('change', filter.text, $event)"
class="form-check-input"
type="checkbox"
:value="filter.text"
:checked="checked"
name="filters"> {{filter.text}}
{{$props | json 2}}</label>
</div>`,
});
new Vue({
el: '#app',
data: {
filterFacets: [
{ id: 0, text: 'filter_a' },
{ id: 1, text: 'filter_b' },
{ id: 2, text: 'filter_c' },
{ id: 3, text: 'filter_d' },
],
selected: [], // How can I let this contain ['filter_a', 'filter_b'] etc. when selected?
},
methods:{
onChange(filter, $event){
if ($event.target.checked)
this.selected.push(filter)
else {
const index = this.selected.findIndex(f => f === filter)
if (index >= 0)
this.selected.splice(index, 1)
}
}
}
});
And change your template to
<div id="app">
<facet-filter
v-for="item in filterFacets"
v-bind:filter="item"
v-bind:checked="selected.includes(item.text)"
:key="item.id"
#change="onChange"
>
</facet-filter>
<p><pre>data: {{$data | json 2}}</pre></p>
</div>
Updated bin.

Categories