VueJS: Updating component collection data from its child - javascript

Given Todo list with filtering:
list.vue:
<script>
import TodoItem from './todo_item.vue';
export default {
components: { TodoItem },
props: ['selectePriority'],
data: {
items: [
{ name: 'Do shopping', priority: 'high' },
{ name: 'Play games', priority: 'low' }
]
},
computed: {
selectedItems: function() {
if(this.selectedPriority == 'all') {
return this.items;
} else {
var selectedPriority = this.selectedPriority;
return this.items.filter(function(item) {
return item.priority == selectedPriority
});
}
}
},
}
</script>
<template>
<div>
<select v-model="selectedPriority">
<option value="all">All</option>
<option value="low">Low</option>
<option value="high">High</option>
</select>
<todo-item
v-for="item in selectedItems"
:name="item.name"
:priority="item.priority"
/>
</div>
</template>
todo_item.vue:
<script>
export default {
props: ['name', 'priority']
}
</script>
<template>
<div>
<p>{{ name }}</p>
<select v-model="priority">
<option value="low">Low</option>
<option value="high">High</option>
</select>
</div>
</template>
html:
<list />
Now, when for example filter is set to all, I change Play games to priority high and change filter to high, I will see only Do shopping, as priority was not updated in items collection and it was re-rendered.
What is proper way to update collection data from child components in Vue.js?

computed properties have the ability to create and return a filtered list.
this example uses lodash
data: {
items: [
{name: 'thing 1', value: 1000},
{name: 'thing 2', value: 50},
{name: 'thing 3', value: 250},
{name: 'thing 4', value: 342},
],
},
computed: {
orderedItems() {
let items = []
return _.orderBy(this.items, 'value', 'desc');
},
}
to update pass the index from orderedItems array into the "this.items" array.

I found some solution that works - instead of passing all params of todo-item into component, pass whole object:
<todo-item
v-for="item in selectedItems"
:item="item"
/>
Then object in parent collection is updated automatically.
Is that a good way of doing that in Vue?

Related

v-bind an array in a menu value but get individual item in the array

Hope you all can help here.
I have a drop-down menu that I'm using v-bind to assign an array to the selected value. See below-
<select v-model="selected" name="SKU[]" #onChange>
<option v-for="infos in data" v-bind:value="{ level: infos.stockLevel, SKU: infos.SKU }" #change="onChange($event)" >{{infos.SKU}} </option>
</select>
There are multiple menus and I'm storing the selected options in SKU[] and then passing them to a form via a post.
The problem is my controller is now reading SKU[] as this:
array:2 [▼
0 => "[object Object]"
1 => "[object Object]"
]
I want to store infos.SKU in SKU[], not object object.
Any idea how I can do this?
You can't store javascript objects in html. You can use there only strings, so you should call JSON.stringify() method with your object and then pass into value. Than you will be able to use it as an object after JSON.parse().
But the best case is to use another vue component for options or pass only id of object in value
Instead of passing an event in #change event, you can directly pass the selected v-model attribute and then push the data object into SKU data variable.
Live Demo :
new Vue({
el: '#app',
data: {
data: [{
stockLevel: 'Level 1',
SKU: 'SKU 1'
}, {
stockLevel: 'Level 2',
SKU: 'SKU 2'
}, {
stockLevel: 'Level 3',
SKU: 'SKU 3'
}, {
stockLevel: 'Level 4',
SKU: 'SKU 4'
}],
selected: '',
SKU: []
},
methods: {
onChange(selectedValue) {
if (!JSON.stringify(this.SKU).includes(JSON.stringify(selectedValue))) {
this.SKU.push(selectedValue);
}
console.log(this.SKU);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select v-model="selected" #change="onChange(selected)">
<option
v-for="(infos, index) in data"
:key="index"
v-bind:value="{ level: infos.stockLevel, SKU: infos.SKU }"
>
{{infos.SKU}}
</option>
</select>
</div>
First of all, I am not sure how are you managing multiple selections because without using the multiple prop or any other way, at a time only a single value would be displayed as selected which seems vague from a "multiple-selection UI" perspective.
Secondly, there is no #onchange syntax, it should be either onchange or #change.
Third, the #change event should fire on the select element, not the option element.
At last, your v-model is what you are using to bind the selected element, then why not use that to track the selected element?
Here is the working demo-
<html>
<div id="app">
{{ SKU }}<br>
<select v-model="selected" #change="onChange">
<option
v-for="infos in data"
:value="{ level: infos.stockLevel, SKU: infos.SKU }"
>{{ infos.SKU }}
</option>
</select>
</div>
<!-- Don't forget to include Vue from CDN! -->
<script src="https://unpkg.com/vue#2"></script>
<script>
new Vue({
el: "#app", //Tells Vue to render in HTML element with id "app"
data() {
return {
SKU: [],
selected: null,
data: [{
stockLevel: "Stock level 1",
SKU: "SKU 1",
},
{
stockLevel: "Stock level 2",
SKU: "SKU 2",
},
],
};
},
methods: {
onChange() {
// Do not push if already exists. I assumed "SKU" was a unique property.
if (this.SKU.find(item => item.SKU == this.selected.SKU)) return;
this.SKU.push(this.selected)
},
},
});
</script>
</html>

VueJs How to make dropdown component to accept different types of props

I want to create a dynamic dropdown component where the amount of dropdowns and their options would be dynamic based on the props provided.
I would like to call the dropdown component and pass an object with the names of individual selects. I want the number of select to be determined by the names provided.
selectNameUsers: { users: "Users", roles: "Roles" } - this should create two dropdowns with the name of users and roles. - THIS HAS BEEN DONE.
Now my question/issue. How can I pass the data for the options. My attempt kinda works but the data provided is:
a) duplicated across multiple selects
b) I had to hard code the v-for= for the data provided so the component is not truly dynamic.
Any ideas?
Code:
https://codesandbox.io/s/adoring-lewin-2pqlq?file=/src/components/parent.vue:0-1245
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<parent />
</template>
<script>
import parent from "./components/parent.vue";
export default {
name: "App",
components: {
parent: parent,
},
};
</script>
parent.vue
<template>
<div class="first">
<custom-dropdown
title="Users Manager"
instructions="Assign Role to User"
:selectName="selectNameUsers"
:users="users"
:roles="roles"
>
</custom-dropdown>
</div>
<div class="second">
<custom-dropdown
title="Cars Manager"
instructions="Look at cars"
:selectName="selectNameCars"
:cars="cars"
>
</custom-dropdown>
</div>
</template>
<script>
import customDropdown from "./customDropdown.vue";
export default {
components: { customDropdown },
data() {
return {
selectNameUsers: { users: "Users", roles: "Roles" },
selectNameCars: { cars: "Cars" },
users: [
{ uid: 1, name: "Ade", role: "standard" },
{ uid: 2, name: "Bab", role: "admin" },
{ uid: 3, name: "Cad", role: "super_admin" },
],
roles: [
{ rid: 1, name: "standard" },
{ rid: 2, name: "admin" },
{ rid: 3, name: "super_admin" },
],
cars: [
{ cid: 1, type: "Audi", colour: "Red" },
{ cid: 2, type: "BMW", colour: "Black" },
],
};
},
};
</script>
customDropdown
<template>
<div>
<h1>{{ title }}</h1>
<select
v-for="(objectvalue, keyName, index) in selectName"
:key="index"
:name="keyName"
>
<option v-for="user in users" :key="user.uid" :value="user.uid">
{{ user.name }}
</option>
<option v-for="role in roles" :key="role.rid" :value="role.uid">
{{ role.name }}
</option>
</select>
<h2>{{ instructions }}</h2>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
},
instructions: {
type: String,
},
selectName: {
type: Object,
},
users: {
type: Object,
},
roles: {
type: Object,
},
},
data() {
return {};
},
};
</script>

Why does't this toggle and filter not work in Vue JS?

I have created a v-for directive and I am now trying to add a dropdown filter in order to filter the results displayed. However, it's just not working. I have followed every step carefully as this is based on a Treehouse tutorial, but for some reason when changing the dropdown nothing displays. Seems the name property value is not being set to the object. Every time a new option is chosen it should fire the function filterList.
const clubs = [
{
name: 'Tigers',
location: 'Manchester',
members: '22',
registered: 'No',
pitch: 'Grass'
},
{
name: 'Dolphins',
location: 'Miami',
members: '19',
registered: 'Yes',
pitch: 'Grass'
},
{
name: 'Bleu Sox',
location: 'Paris',
members: '13',
registered: 'Yes',
pitch: 'Astroturf'
}
];
const app = new Vue({
el: '#app',
data: {
clubs,
name: ''
},
methods: {
toggleDetails: function(club) {
this.$set(club, 'showDetails', !club.showDetails)
},
filterList: function() {
this.name = event.target.value;
console.log(this.name);
}
}
});
My HTML is as follows;
<div id="app">
<select v-on:change="filterList">
<option v-for="club in clubs">{{club.name}}</option>
</select>
<ul>
<li v-show="name === club.name" v-for="club in clubs" v-on:click="toggleDetails(club)">
<h1>{{club.name}}</h1>
<div v-show="club.showDetails">
<p>{{club.location}}</p>
<p>{{club.members}}</p>
</div>
</li>
</ul>
</div>
You don't need to access DOM events for this - Vue is reactive and will update name when this is changed:
<select v-model="name">
<option v-for="club in clubs">{{club.name}}</option>
</select>

Multiple select Vue.js and computed property

I'm using Vue.js 2.0 and the Element UI library.
I want to use a multiple select to attribute some roles to my users.
The list of all roles available is received and assigned to availableRoles. Since it is an array of object and the v-model accepts only an array with value, I need to extract the id of the roles trough the computed property computedRoles.
The current roles of my user are received and assigned to userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}].
computedRoles is then equals to [1,3]
The preselection of the select is fine but I can't change anything (add or remove option from the select)
What is wrong and how to fix it?
http://jsfiddle.net/3ra1jscx/3/
<div id="app">
<template>
<el-select v-model="computedRoles" multiple placeholder="Select">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}]
}
},
computed : {
computedRoles () {
return this.userRoles.map(role => role.id)
}
}
}
I agree mostly with #wostex answer, but he doesn't give you the userRoles property back. Essentially you should swap computedRoles and userRoles. userRoles becomes a computed property and computedRoles is a data property. In my update, I changed the name of computedRoles to selectedRoles.
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
selectedRoles:[1,2]
}
},
computed : {
userRoles(){
return this.availableRoles.reduce((selected, role) => {
if (this.selectedRoles.includes(role.id))
selected.push(role);
return selected;
}, [])
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
And here is the fiddle.
Check the solution: jsfiddle
The caveat here is that computed properties are getters mainly. You can define setter for computed property, but my approach is more vue-like in my opinion.
In short, instead of v-model on computed set v-model for data property.
Full code:
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-select v-model="ids" multiple placeholder="Select" #change="logit()">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}],
ids: []
}
},
mounted() {
this.ids = this.userRoles.map(role => role.id);
},
methods: {
logit: function() {
console.log(this.ids);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

VueJS access model from method

In VueJS I am setting model data based on user actions. I want to access the model from a method to update an element.
In the code below, when the user changes the first select list, I want to update the second select list to show the id property of the first list. As it is the upper list works OK but the lower list id property is not updated on upper list change:
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
</head>
<body>
<div id="editor">
<form id="query" methods="GET">
<div id="form_container" class="row">
<div class="form-group">
<label for="choice-selector">Choices</label>
<select class="form-control" id="choice-selector" v-model="choice_id" v-on:change="refreshOptions">
<option v-for="item in choices" v-bind:value="item.id">
{{ item.name }}
</option>
</select>
<span>Current choice id: {{ choice_id }}</span>
<br>
<label for="option-selector">Options</label>
<select class="form-control" id="option-selector" v-model="option_id" >
<option v-for="item in options" v-bind:value="item.id">
{{ item.name }}
</option>
</select>
<span>Current option id: {{ option_id }}</span>
</div>
</div>
</div>
<script>
let index = 0;
new Vue({
el: '#editor',
data: {
choice_id: '1',
choices: [
{ id: '1', name: 'Choice A' },
{ id: '2', name: 'Choice B' },
{ id: '3', name: 'Choice C' }
],
option_id: '1',
options: [
]
},
ready: function startFetch() {
this.refreshOptions();
},
methods: {
refreshOptions: function refreshOptionList() {
console.log(">>refreshOptionList() index:" + index);
const vm = this;
const newOptions = [{ id: index, name: 'Option based on choices list id: ' + vm.choice_id }];
vm.$set('options', newOptions);
index += 1;
}
},
});
</script>
</body>
</html>
Any ideas?
In Vue 2.x vm.$set is an alias for Vue.set and it takes 3 parameters: target, key and value so you should use it like this:
vm.$set(this, 'options', newOptions);
Or you can just assign newOptions to this.options
this.options = newOptions;
Working example: https://plnkr.co/edit/lFDm7wxb56h81EAwuUNc

Categories

Resources