Vue,js, Computing a property with v-model? - vue.js

I have a vue page that loads a bunch of actions from a database. It then creates a table with some data got from the action, using v-for.
Here is my problem: In one of the table rows I need a checkbox that is v-modeled to an attribute, action.weekly. Ideally, it will be a boolean. But there are a bunch of action entries in my database that don't have a weekly attribute. In those cases, I need the checkbox checked, as if it were true. Normally, I would use a computed property here, but you can't pass arguments to computed properties, so I don't know how to tell vue which action to look at at (I can't pass $event, ndx in to a computed property like I am doing below with handleEnableChanged() ).
Here is my code for the table:
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='openModalCard(action, ndx)'>
{{action.name}}
</td>
<input type="checkbox" v-model="action.weekly??" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
In the cases where action does not have a weekly attribute, I want the checkbox checked as if it were true. How can I accomplish this?
If there is a better way to approach this, please let me know. I'm still a novice with vue.js.

I think it would be easiest to use v-if and v-else for this.. With that being said, I have also provided an example of how to handle this without v-if and v-else..
Using v-if and v-else:
new Vue({
el: "#root",
data: {
actions: [
{
name: "first",
weekly: true,
enabled: false
},
{
name: "second",
enabled: false
},
{
name: "third",
weekly: true,
enabled: true
},
{
name: "fourth",
enabled: true
}
]
},
template: `
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='console.log(action, ndx)'>{{action.name}}</td>
<input v-if="'weekly' in action" type="checkbox" v-model="action.weekly" #change="handleEnableChanged($event, ndx)"/>
<input v-else type="checkbox" checked="true" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
`,
methods: {
handleEnableChanged(evt, ndx) {
console.log(evt, ndx);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="root"></div>
Without v-if and v-else:
new Vue({
el: "#root",
data: {
actions: ""
},
template: `
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='console.log(action, ndx)'>{{action.name}}</td>
<input type="checkbox" v-model="action.weekly" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
`,
methods: {
handleEnableChanged(evt, ndx) {
console.log(evt, ndx);
},
getActions() {
let originalActions = [
{
name: "first",
weekly: true,
enabled: false
},
{
name: "second",
enabled: false
},
{
name: "third",
weekly: true,
enabled: true
},
{
name: "fourth",
enabled: true
}
];
this.actions = originalActions.map(a => {
return {
name: a.name,
weekly: 'weekly' in a ? a.weekly : true,
enabled: a.enabled
}
})
}
},
created() {
this.getActions();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="root"></div>

You can just do this:
<input type="checkbox" :checked="!!action.weekly">
The !! operator will return true if the value is not undefined or empty

If you want all items in the array to have an action.weekly property, then update your data when it is initially retrieved. You can then just use v-model="action.weekly" for all items in the array.
this.newArray = this.oldArray.map(item => {
if(!item.hasOwnProperty('weekly')){
item.weekly = true;
}
return item
})

Related

Vue 3 problems with value input

I have two components, the first one is a row insertion table and the second one contains the rows
I have a problem with the inputs, everything works fine except that when I delete a line in the middle of the table the values of the other inputs are reset,
the parent component :
<script>
import editortable from "./editortable.vue";
export default {
data: () => {
return {
elements: [{ id: 0, ref: "1", equation: "2" }],
nextid: "1",
};
},
methods: {
removrow(index) {
this.elements.splice(index, 1);
},
add() {
this.elements.push({
id: this.nextid++,
ref: "",
});
},
},
components: {
editortable,
},
};
</script>
<tbody>
<tr
is="vue:editortable"
v-for="(element, index) in elements"
:iden="element.id"
:referen="element.ref"
:eq="element.equation"
:key="index"
#delete="removrow(index)"
></tr>
</tbody>
<button type=" button" class="btn btn-primary" #click="add">
Ajouter une ligne
</button>
** component :**
<script>
export default {
props: ["iden", "referen", "eq"],
emits: ["delete"],
methods: {
deleterow() {
this.$emit("delete");
},
},
};
</script>
<template>
<tr>
<td>
<input class="form-control" type="ipunt" :value="iden" />
</td>
<td><input class="form-control" type="ipunt" :value="referen" /></td>
<td><input class="form-control" type="ipunt" :value="eq" disabled /></td>
<td>
<button type="button" class="btn btn-danger" #click="deleterow">
delete
</button>
</td>
</tr>
</template>
There is 1 obvious issue is you shouldn't use index as the key in your case.
Change it to use the id.
:key="element.id"
And in removrow(id), using id find the index first and then remove it should be ok.
removrow(id) {
const index = this.elements.findIndex(ele => ele.id === id);
this.elements.splice(index, 1);
}

vue2 list return mixed string or component

In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?
Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

vue.js how to v-model values as separate arrays

from the backend I'm getting an array like this.
then I render this array to a table like this
My code
<tr v-for="item in items">
<td>
{{item[1]}}
</td>
<td>
{{item[2]}}
</td>
<td>
<input type="text" v-model="grnItems[items[1]]"/>
</td>
</tr>
This is a purchase return component
what I want is v-model this each an every input element as a separate array along with the item name.
like this
[
["chicken","12"]
["chille","19"]
]
How do I achieve this using vue.js?
Use an auxiliar array with the data populated the way you want, some example using computed properties
new Vue({
el: '#app',
data: {
items: [['1', 'some text', '66'], ['2', 'another text', '12'], ['5', 'random text', '89']],
result: []
},
computed: {
procesedItems() {
return this.items.map(i => ({
id: i[0],
name: i[1],
amount: i[2]
}))
}
},
methods: {
doSomething() {
this.result = this.procesedItems.map(i => {
let aux = [];
aux.push(i.name, i.amount)
return aux
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in procesedItems"> {{item.id }} {{item.name }} <input v-model="item.amount"/></li>
</ul>
<button #click="doSomething">Calculate</button>
{{ result }}
</div>

Vue 2 - change data value of all components

I have following setup:
Vue code:
Vue.component('ordering-filters', {
template: `
<a href="#"
:class="iconClass + faClass"
aria-hidden="true"
#click="orderCountries({orderBy, order})">
</a>`,
props: {
orderBy: {
type: String,
required: true
},
order: {
type: String,
required: true
},
iconClass: {
type: String,
default: "fa fa-lg text-muted"
},
faClass: {
type: String,
required: true
}
},
methods: {
orderCountries(params){
Event.$emit('ordering-filters', params);
}
},
data() {
return {
isActive: false
}
}
});
HTML code:
<tr>
<td class="col-md-6">Country Name
<div class="arrow-group">
<ordering-filters
order-by="name"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="name"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td class="countries-visible-filter col-md-3">
<visible-filters></visible-filters>
</td>
<td>Order
<div class="arrow-group">
<ordering-filters
order-by="order"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="order"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td>Actions</td>
</tr>
I want to change isActive to all the ordering-filters components when click event is fired, set it to false for all and then set it to true on the clicked element. Is this possible?
You must delegate the control of the active element to the parent somehow. Here is one of the many ways to do that, using v-model (which is just a shortcut for :value and #input), and a simple computed prop.
Vue.component('ordering-filters', {
template: `<a href="#" #click="orderCountries()">
{{ filterId }}
<template v-if="isActive"> I'm on. </template>
</a>`,
props: ['value', 'filterId'],
computed: {
isActive() {
return this.value === this.filterId;
}
},
methods: {
orderCountries() {
// Do some ordering stuff
this.$emit('input', this.filterId); // The active element is now this one
}
}
});
new Vue({
el: '#app',
data() {
return {
activeFilterId: null
};
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<ordering-filters filter-id="one" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="two" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="three" v-model="activeFilterId"></ordering-filters>
</div>