Passing an Argument Value to the Data Object as a Key in Vuejs - vue.js

I'm creating a page that contains 4 different tables for now. All rows in each table will have update and delete buttons. I can make everything work when I code all CRUD functions for each table separately. But what I want to do is create a function for update action for example, and pass the table name as an argument.
<tr v-for="asset in assets" :key="asset.id">
<td>{{asset.id}}</td>
<td>{{asset.name}}</td>
<td>{{asset.category.name}}</td>
<td>
<a href #click.prevent="editDialog(asset, 'asset')">Edit</a>
<a href #click.prevent="deleteThis(asset.id)">Delete</a>
</td>
</tr>
This is the pop-up window for edit and create an item:
<v-dialog v-model="dialog">
<div v-if="editMode">
<div v-if="target_table == 'asset'">
<form>...</form>
</div>
</div>
<div v-else>...</div>
</v-dialog>
I have those things in script
export default {
data() {
return {
dialog: false,
editMode: false,
target_table: "",
assets: {},
asset: new Form({
id: "",
name: "",
category: "",
}),
}
},
methods: {
fetchAssets() {
axios.get("/api/assets").then(({ data }) => (this.assets = data));
},
editDialog(data, table) {
this.target_table = table;
this.editMode = true;
this.dialog = true;
this.table.fill(data);
},
},
}
And here is my problem. If you look at the last row of the code, I'm trying to fill a form named "asset" that comes with editDialog(asset, 'asset') as its second parameter. But with this syntax, Vuejs goes to data object and look for a "table" as a key instead of "asset" and I'm getting "table is not defined" error. How can I make it work? Thanks.

I suggest you to change this.table.fill(data); with this.$data[table].fill(data)
Let me know if it works

Related

changing a single value using v-model / full table is redrawn

I was building an editable table, which began to crawl to a halt when the number of rows started to run in the 100's. This led me to investigate what was going on.
In the example below, when changing the value in the input, the whole table is redrawn, and the ifFunction() function is trigged 4 times.
Why is this happening? Shouldn't Vue be capable of just redrawing the respective cell? Have I done something wrong with the key-binding?
<template>
<div id="app">
<table border="1" cellpadding="10">
<tr v-for="(row, rowKey) in locations" :key="`row_+${rowKey}`">
<td v-for="(column, columnKey) in row" :key="`row_+${rowKey}+column_+${columnKey}`">
<span v-if="ifFunction()">{{ column }}</span>
</td>
</tr>
</table>
<input v-model="locations[0][1]">
</div>
</template>
<script>
export default {
data() {
return {
locations: [
["1","John"],
["2","Jake"]
], // TODO : locations is not generic enough.
}
},
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
}
}
</script>
The data property defines reactive elements - if you change one part of it, everything that's depending on that piece of data will be recalculated.
You can use computed properties to "cache" values, and only update those that really need updating.
I rebuilt your component so computed properties can be used throughout: created a cRow and a cCell component ("custom row" and "custom cell") and built back the table from these components. The row and the cell components each have a computed property that "proxies" the prop to the template - thus also caching it.
On first render you see the ifFunction() four times (this is the number of cells you have based on the data property in Vue instance), but if you change the value with the input field, you only see it once (for every update; you may have to click "Full page" to be able to update the value).
Vue.component('cCell', {
props: {
celldata: {
type: String,
required: true
},
isInput: {
type: Boolean,
required: true
},
coords: {
type: Array,
required: true
}
},
data() {
return {
normalCellData: ''
}
},
watch: {
normalCellData: {
handler: function(value) {
this.$emit('cellinput', {
coords: this.coords,
value
})
},
immediate: false
}
},
template: `<td v-if="ifFunction()"><span v-if="!isInput">{{normalCellData}}</span> <input v-else type="text" v-model="normalCellData" /></td>`,
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
},
mounted() {
this.normalCellData = this.celldata
}
})
Vue.component('cRow', {
props: {
rowdata: {
type: Array,
required: true
},
rownum: {
type: Number,
required: true
}
},
template: `
<tr>
<td
is="c-cell"
v-for="(item, i) in rowdata"
:celldata="item"
:is-input="!!(i % 2)"
:coords="[i, rownum]"
#cellinput="reemit"
></td>
</tr>`,
methods: {
reemit(data) {
this.$emit('cellinput', data)
}
}
})
new Vue({
el: "#app",
data: {
locations: [
["1", "John"],
["2", "Jake"]
], // TODO : locations is not generic enough.
},
methods: {
updateLocations({
coords,
value
}) {
// creating a copy of the locations data attribute
const loc = JSON.parse(JSON.stringify(this.locations))
loc[coords[1]][coords[0]] = value
// changing the whole locations data attribute to preserve
// reactivity
this.locations = loc
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table border="1" cellpadding="10">
<tbody>
<tr v-for="(row, i) in locations" is="c-row" :rowdata="row" :rownum="i" #cellinput="updateLocations"></tr>
</tbody>
</table>
<!-- <input v-model="locations[0][1]">
<input v-model="locations[1][1]">-->
{{locations}}
</div>

Mutating a value in vue when the key didn't previously exist does not update the view

I have a table and a select box for each row. I want the check box to model a value in the data that doesn't actually exist, yet.
<tr v-for="item in someData">
<input type="checkbox" v-model="item.selected"></td>
<input type="checkbox" v-model="item.name"></td>
<tr>
My data when loaded from the DB looks like this:
someData: [
{'name': 'john'},
{'name': 'kate'},
{'name': 'aaron'},
]
When the user presses a Select All button it should update the selected key even if it doesn't exist (well thats the idea)
toggleSelect: function () {
this.someData.forEach(element => {
element.selected = !element.selected;
});
}
However the checkboxes don't react even though the values have been updated. To make this work I need to get the data and add the key/value manually prior to loading it into view and rendering
getDatabaseData: function () {
// some code omitted
response['data'].forEach(element => {
element["selected"] = false;
});
app.someData = response['data']
}
Am I doing it correctly? Am I right in thinking Vue won't be reactive to values that didn't exist prior to rendering?
Try this idea,
in vue component.
<input type="checkbox" v-model="selectAll"> Select All
<tr v-for="item in someData" :key="item.name">
<td>
<input type="checkbox" v-model="selected" :value="item.name">
</td>
{{ item.name }}
</tr>
script:
data() {
return {
selectAll: false,
selected: [],
someData: [{ name: "john" }, { name: "kate" }, { name: "aaron" }]
};
},
watch: {
selectAll(value) {
// validate if value is true
if (value) {
this.someData.forEach(item => {
// push unique value
if(this.items.indexOf(item.name) === -1) {
this.selected.push(item.name);
}
});
} else {
// Unselect all
this.selected = [];
}
}
}
You have a selected variable where the selected Items are located. selectAll variable to select all items and push to selected variable.
You should be using Vue.set to update the value of the selected property on your objects in order to be reactive, like this:
import Vue from 'vue';
...
toggleSelect: function () {
this.someData.forEach(element => {
Vue.set(element, 'selected', !element.selected);
});
}

VueJS - pass data to component with existing row

Here's my fiddle
data: {
address_components: [
{ id:1, city_id:1, area_id: 100}
],
city_id:0,
area_id:0,
sort:1
},
I need to pass data to existing row, is that incorrect?
And if I add a new row, the id should be '2'
like this
what is the good way to do this? :'-(
modify your code like this:
addComponent() {
this.address_components.push({id:++this.sort})
},
How about creating a list in your data() and just push a object there.
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: []
},
methods:
{
Add:function()
{
//process the inputs and push it to items list
this.items.push({ message: "Hi"})
}
}
})
Did not tested this and just created from example from Vue Documentation but you got the idea. Pushing a new item in list will automatically updates the component or the element

Array Reactivity Issues

I'm having issues with array reactivity, please see this example: https://jsfiddle.net/jk1kadxq/
var app = new Vue({
el: "#app",
data: function () {
return {
grid: {
rows: [{}]
}
}
},
methods: {
addRow: function () {
this.grid.rows.push({});
},
setRow: function (row) {
console.log(row);
this.$set(row, 'cell', 'Test');
}
},
watch: {
'grid.rows': {
deep: true,
handler: function (rows, oldRows) {
console.log('Rows updated', rows, oldRows);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<table>
<tr v-for="row in grid.rows">
<td><input type="text" v-model="row.cell"></td>
<td><button type="button" #click="setRow(row)">Set</button></td>
</tr>
</table>
<button type="button" #click="addRow">Add</button>
</div>
If a row has not been edited manually, clicking "Set" button sets the field to "Test" and all the further updates to it are catched in watcher.
If a row has been edited manually first, watcher is not triggered, and clicking "Set" button does not immediately update the field. Adding another row updates the current row.
Is there a different way to add new array members? This page says it's ok to just add: https://v2.vuejs.org/v2/guide/list.html
I've figured out the issue... All the properties of objects should be initialized.
So in my case, the proper way to add a row is: this.grid.rows.push({cell: ''});
It would be nice though to initialize object properties when binding to controls, if not initialized yet.

How to amend data in a different component?

I am new to Vue js and trying to build a simple CRUD example for myself.
Following the documentation on non parent child communication I would like to amend the heading value in the data of one component but from another component.
I set up a fiddle to showcase the relative functionality as I currently understand it and we have here the HTML:
<div id="app" v-cloak>
<person-add></person-add>
<person-list :list="people"></person-list>
</div>
<template id="person-add-template">
<div>
<h2>
<span>{{ heading }}</span>
Person
</h2>
<form #submit.prevent="handleFormSubmit">
<input type="text" placeholder="Enter persons name" v-model="name" />
<button type="submit" v-show="name">
Add Person
</button>
</form>
</div>
</template>
<template id="person-list-template">
<div>
<h2>People</h2>
<table border="1">
<tr>
<th>Person</th>
<th>Edit</th>
</tr>
<tr v-for="(person, key) in list">
<td>{{ person.name }}</td>
<td><button type="button" #click="editPerson(key)">Edit</button></td>
</tr>
</table>
</div>
</template>
And the JS:
// https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
var bus = new Vue();
// Add
Vue.component('person-add', {
template: '#person-add-template',
props: ['list'],
data: function () {
return {
heading: 'Add',
name: ''
}
},
created: function () {
bus.$on('toggle-heading', function (newHeading) {
console.log(newHeading);
this.heading = newHeading;
});
}
});
// List
Vue.component('person-list', {
template: '#person-list-template',
props: ['list'],
methods: {
editPerson: function (key) {
console.log('fired');
bus.$emit('toggle-heading', 'Edit');
}
}
});
// Vue
new Vue({
el: '#app',
data: {
people: [
{ name: 'Bob' },
{ name: 'Frank' },
{ name: 'Mary' }
]
}
});
As you can see, it presents a simple form that starts with "Add Person" and lists some people along with an edit button for each:
What I would like to happen is that when I click on edit next to a persons name then it will change the heading in the other component to say "Edit Person" as opposed to the default "Add Person".
In the method in component A I have:
editPerson: function (key) {
console.log('fired');
bus.$emit('toggle-heading', 'Edit');
}
And in the created hook within component B I have:
created: function () {
bus.$on('toggle-heading', function (newHeading) {
console.log(newHeading);
this.heading = newHeading;
});
}
When I click edit, in the console I see the logs fired and then Edit so the event seems to follow through to the person-add component but where I have tried to assign the new heading this.heading = newHeading;, the heading does not change and I am battling to understand why.
If anyone could suggest why this is happening, where I have gone wrong with this or how things should be done if this is not the right way then it would be greatly appreciated.
Many thanks in advance!
Your problem is actually to do with scope, not a lack of understanding of Vue. Your code is correct, except you are trying to access this from inside a function creating it's own this context.
Whenever you create a new function this way, it creates it's own this so when you do:
bus.$on('toggle-heading', function(newHeading) {
console.log(newHeading);
// this refers to this anonymous function only
this.heading = newHeading;
});
this only refers to the function itself, not the the Vue instance.
The way to get around this is to use an arrow function, which do not create their own this:
bus.$on('toggle-heading', (newHeading) => {
console.log(newHeading);
// No new 'this' context is created in an arrow function
this.heading = newHeading;
});
Or if you are not using ECMAScript 2015 you will need to set a reference to this outside the function:
var self = this; // set a reference to "this"
bus.$on('toggle-heading', function(newHeading) {
console.log(newHeading);
// Now self refers to the view models `this`
self.heading = newHeading;
});
I've updated your fiddle to show you the two methods:
Arrow Function: https://jsfiddle.net/abtgmx47/3/
Using var self=this reference: https://jsfiddle.net/abtgmx47/4/