v-if and v-else not working after data array change - vue.js

Depending on the array 'r.meta.fields' a specific sort icon of each column needs to be shown. When the template is rendering, it is working correctly. But when the array change, the template isn't changing anymore.
<th v-for="field in r.meta.fields">
{{field.label}}
<a href="#" #click.prevent="sortField(field)">
<div class="fa fa-sort-up" v-if="field.sort_direction === 'desc'"></div>
<div class="fa fa-sort-down" v-else-if="field.sort_direction === 'asc'"></div>
<div class="fa fa-sort" v-else-if="field.sortable"></div>
</a>
What could be the problem?

You could create a mapping for the sort icons and handle the changes on click:
const vm = new Vue({
el: '#app',
data() {
const iconMap = {
sort: {
'asc': 'fa-sort-up',
'desc': 'fa-sort-down'
}
};
return {
r: {
meta: {
fields: [
{
label: 'field #1',
sortable: false,
sort_direction: 'asc',
icon: ''
},
{
label: 'field #2',
sortable: true,
sort_direction: 'desc',
icon: iconMap.sort['desc']// Initially sortable in descending order
}
]
}
},
iconMap
}
},
methods: {
sortField(field) {
let direction = (field.sort_direction === 'asc') ? 'desc' : 'asc';
let icon = this.iconMap.sort[direction] || '';
field.sort_direction = direction;
field.icon = icon;
}
}
})
Template or HTML
<div id="app">
<table>
<tr>
<th v-for="field in r.meta.fields" :key="field.label">
{{field.label}}
<a href="#"
:class="field.icon"
#click.prevent="sortField(field)"></a>
</th>
</tr>
</table>
</div>

if you are using something like
r.meta.fields = newValue
then this won't work.
you should use
Vue.set(r.meta.fields, indexOfItem, newValue)
document here: vue update array

Related

Change ordering of a array of objects with up/down buttons

I'm having the following issue:
I want in the frondend to get a list of items based on the key 'order':
<div id="app">
<ul>
<li v-for="item in sorted">
{{item.order}} {{item.title}}
<button #click="changeOrderDown(item)">down</button>
<button #click="changeOrderUp(item)">up</button>
</li>
</ul>
</div>
based on the JSON you see below. When you click the button I want to swap out for example order 1 -> 2 and then 2 becomes 1
items: [
{
title: "test",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
I keep getting a duplicate key error cause i first change the first key and then the second. i resolved it to update the whole object at ones. that seems to work but still it doesn't behave the correct way.
computed: {
sorted() {
function compare(a, b) {
let comparison = 0;
if (a.order > b.order) {
comparison = 1;
} else if (a.order < b.order) {
comparison = -1;
}
return comparison;
}
return this.items.sort(compare)
},
},
methods: {
changeOrderDown(currentItem) {
let temp = this.items
let old_value = parseFloat(currentItem.order)
let new_value = parseFloat(currentItem.order) + 1;
console.log(old_value, new_value)
temp.filter(o => o.order === old_value)[0].order = new_value;
temp.filter(o => o.order === new_value)[0].order = old_value;
this.items = temp;
},
changeOrderUp(currentItem) {
let temp = this.items
let old_value = parseFloat(currentItem.order)
let new_value = parseFloat(currentItem.order) - 1;
console.log(old_value, new_value)
temp.filter(o => o.order === old_value)[0].order = new_value;
temp.filter(o => o.order === new_value)[0].order = old_value;
this.items = temp;
},
}
I made a codepen down below with the code from above. this is kinda a working example but it doesn't feel right. Can someone give me a push in the right direction?
https://codepen.io/frank-derks/pen/BaQVOZV
Interesting challenge. Using my Vue 2 CLI sandbox app, I came up with functionality that doesn't require an 'order' property. Here is the component code:
<template>
<div class="swap-array-objects">
<h3>SwapArrayObjects.vue</h3>
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>TITLE</th>
<th> </th>
<th> </th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.title }}</td>
<td>
<button class="btn btn-secondary btn-sm" #click="moveUp(index)">Up</button>
</td>
<td>
<button class="btn btn-secondary btn-sm" #click="moveDown(index)">Down</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
title: 'title1'
},
{
title: 'title2'
},
{
title: 'title3'
},
{
title: 'title4'
},
{
title: 'title5'
}
]
}
},
methods: {
moveUp(index) {
if (index === 0) {
return;
}
let priorIndex = index - 1;
let itemCopy = {...this.items[index]};
let priorItemCopy = {...this.items[priorIndex]};
// Swap array position with prior element
this.$set(this.items, priorIndex, itemCopy);
this.$set(this.items, index, priorItemCopy);
},
moveDown(index) {
if (index === this.items.length - 1) {
return;
}
let subsequentIndex = index + 1;
let itemCopy = {...this.items[index]};
let subsequentItemCopy = {...this.items[subsequentIndex]};
// Swap array positions with subsequent element
this.$set(this.items, subsequentIndex, itemCopy);
this.$set(this.items, index, subsequentItemCopy);
}
}
}
</script>
This solution is similar to Tim's, but a bit simpler and easier to follow:
<template>
<v-app>
<ul>
<li v-for="(item, index) in items" :key="index">
{{item.order}} {{item.title}} {{index}}
<button #click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
<button #click="changeOrderUp(item, index)" v-if="index != 0">up</button>
</li>
</ul>
</v-app>
</template>
<script>
export default {
name: 'App',
data: () => ({
items: [
{
title: "test1",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
}),
methods: {
changeOrderDown(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index + 1]
// move clicked item to destination
this.items[index + 1] = temp
},
changeOrderUp(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index - 1]
// move clicked item to destination
this.items[index - 1] = temp
},
}
};
</script>
<template>
<v-app>
<ul>
<li v-for="(item, index) in items" :key="index">
{{item.order}} {{item.title}} {{index}}
<button #click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
<button #click="changeOrderUp(item, index)" v-if="index != 0">up</button>
</li>
</ul>
</v-app>
</template>
<script>
export default {
name: 'App',
data: () => ({
items: [
{
title: "test1",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
}),
methods: {
changeOrderDown(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index + 1]
// move clicked item to destination
this.items[index + 1] = temp
},
changeOrderUp(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index - 1]
// move clicked item to destination
this.items[index - 1] = temp
},
}
};
</script>

Value of <Input in Vue component not getting value of method

I have a list of users. Click on a specific user the user edit form is populated but the only way I can get a value in the input is by putting the return in the
<input v-model="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP.User_Name" ref=User_Name>
if I do
<input v-model="User_Name "v-bind:value="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP.User_Name">
Nothing appears in the input.
If I use the v-model="this.cl.data....." the value of the user_name is in the input I'm not sure how the value is passed to my updateUser() function because normally I would use username = this.User_Name
Using apollo and Graphgl for the querying
<template>
<div>
<v-text-field label="User Name" ref="User_Name" v-model="User_Name" v-bind:value="this.cl.data.USER_RLTNP_SCTY_ACCS_GRP[0].User_Name" id="" placeholder="User Name"></v-text-field>
<v-btn v-on:click="editUser()">Edit</v-btn>
</div>
</template>
<script>
import gql from graphql-tag import { SCTY_ACCS_GRP, SCTY_ACCS_GRP_USER }
from './gqlqueries'
export default {
data: () => ({
users: [],
cl: '',
user_form: false,
user_name: ''
}),
methods: {
editForm: async function(userid){
this.cl = await this.$apollo.query({query : SCTY_ACCS_GRP_USER, variables: {id : userid}})
this.user_form = true
console.log(this.cl)
alert(this.cl.data.USER_RLTNP_SCTY_ACCS_GRP[0].User_Name)
},
editUser(){
this.user_name = this.User_Name
alert(this.user_name)
}
},
mounted: async function() {
this.users = await this.$apollo.query({ query: SCTY_ACCS_GRP })
// console.log(this.users.data.USER_RLTNP_SCTY_ACCS_GRP)
} }
</script>
I would assume the <input v-bind:value="this.cl.data...."> Would populate once The editForm() function is triggered. So how do I get the value of the User_Name when the editUser button is clicked
You could try list rendering.
A little example :
https://jsfiddle.net/L4xu9r5g/7/
var mapp = new Vue({
el: "#app",
data: {
currentuserinfo : undefined,
users: [
{ name: "John Doe", id : 12},
{ name: "Black Jack", id : 932},
{ name: "White Jack", id: 342}
],
userinfo:
[
{ userid: 12, favcolor: 'green', age : 23},
{ userid: 932, favcolor: 'red', age : 11},
{ userid: 342, favcolor: 'blue', age : 12}
]
},
methods:
{
selectuser : function(id)
{
//your query here
this.currentuserinfo = this.userinfo.filter(u => u.userid ==id)[0];
}
}
})
td, th
{
padding: 5px;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr>
<th>Name</th>
<th>Select User</th>
</tr>
<tr v-for="user in users" :key="user.id">
<td>{{user.name}}</td>
<td><button #click="selectuser(user.id)">Select User</button></td>
</tr>
</tbody>
</table>
<div v-if="currentuserinfo === undefined">
Please select a user
</div>
<div v-if="currentuserinfo != undefined">
<span>Favourite color : {{currentuserinfo.favcolor}}</span><br/>
<span>Age : {{currentuserinfo.age}}</span><br/>
</div>
</div>

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.js reactivity inner mechanisms

Given the following Vue code, how does it know to render the v-for again when selected is changed?
It makes complete sense to me when todos is changed.
So, Vue notices that there's a method isSelected involved and then uses "reflection" to watch the selected value, since it's an instance value?
Is that what's happening under the hood?
<div id="app">
<ol>
<li v-for="todo in todos" :class="{ 'selected': isSelected(todo.text) }">
{{ todo.text }}
</li>
</ol>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
todos: [
{ text: 'foo' },
{ text: 'bar' },
{ text: 'quz' }
],
'selected': 'bar'
},
methods: {
isSelected: function(text) {
return text != this.selected;
}
}
})
app.todos.push({ text: 'test' });
app.todos[0].text = 'change';
app.selected = 'foo';
</script>

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>