Multiple select Vue.js and computed property - javascript

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')

Related

computed filter function in array of names in JS

I created small piece of code to search from given array in search bar. As i unserstood it is better to use computed function over method but when I put function in it dosen't work and give me an error: Duplicate keys detected: '3'. This may cause an update. So how can I solve it?
My function:
data() {
return {
RMnames: [],
filteredNames: [
{ name: "Zales", category: "Jewelry", id: 1 },
{ name: "Lux", category: "Jewelry", id: 2 },
{ name: "Edited", category: "Jewelry", id: 3 },
{ name: "Editedsdsdxs", category: "Jewelry", id: 3 },
],
searchTerm: "",
Names: [],
};
},
computed: {
filteredFunction() {
this.Names = this.filteredNames.filter((n) => {
return n.name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
});
},
},
My template:
<v-col align-self="start">
<v-text-field
v-model="searchTerm"
placeholder="Search"
#input="filteredFunction"
></v-text-field>
<v-card v-for="item in filteredNames" :key="item.id">
<v-card-title>{{ item.name }}</v-card-title>
</v-card>

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>

How to check whether a v-checkbox is selected in Vue JS?

I have a checkbox with the following events and props:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="selectedPlanets"
/>
Given that all of the checkboxes are brought in using a v-for, how can I check whether a checkbox is selected using a method or mounted function in Vue JS?
For example:
methods: {
checkSelected() {
????
},
Add a planets.selected key.
allPlanets: [
{ name: 'Planet name', value: 'Planet value' , selected : false },
...,
...
],
}
And in your template:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="planets.selected"
/>
Similar to:
Display multiple checkbox in table format using <v-for> and <v-checkbox> in Vuetify?
you have to make such a structure, so you know for each id, whether it is checked or not
new Vue({
data: () => ({
allPlanets: [
{
id: 32,
name: "planent",
selected: false
},
{
id: 365,
name: "planet 2",
selected: false
}
],
}),
methods: {
checkSelectedByIndex(index) {
return this.allPlanets[index].selected
},
checkSelectedById(id) {
return this.allPlanets.find(p => p.id === id)?.selected ?? false
}
}
});
and in the you have to set the v-model="planets.selected"
Given your layout the simplest methods is:
methods: {
checkSelected(id) {
return this.selectedPlanets.includes(id)
},
}

Data not being passed to Vue component

I have a Vue component receiving an array of 'items' from its parent.
I've sorted them into categories, two 'items' in each category:
computed: {
// sort items into categories
glass: function() {
return this.items.filter(i => i.category === "glass").slice(0, 2);
},
ceramics:
// etc...
I need to place both items in categories.items to then pass them as props to another component:
data() {
return {
categories: [
{ name: "Glass", sort: "glass", items: {} },
{ name: "Ceramics", sort: "ceramics", items: {} },
{ name: "Brass", sort: "brass", items: {} },
{ name: "Books/Comics", sort: "books", items: {} },
{ name: "Collectibles", sort: "collectibles", items: {} },
{ name: "Pictures", sort: "pictures", items: {} },
{ name: "Other", sort: "other", items: {} }
]
};
},
When I use created or mounted nothing is passed through, when I use beforeDestroy or destroy and console.log the results it works fine, but, they're of no use when exiting the page.
The 'items' are from an Axios GET request, could this be why?
GET request from parent component:
methods: {
fetchItems() {
// items request
let uri = "http://localhost:8000/api/items";
this.axios.get(uri).then(response => {
// randomize response
for (let i = response.data.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[response.data[i], response.data[j]] = [
response.data[j],
response.data[i]
];
}
this.items = response.data;
});
}
},
Passing props to child component:
<div
class="items-container"
v-for="category in categories"
:key="category.name"
>
<router-link :to="'category.link'" class="category-names-home-link">
<h2 class="category-names-home">{{ category.name }}</h2>
</router-link>
<router-link
:to="'category.link'"
class="home-view-all"
#mouseover.native="expand"
#mouseout.native="revert"
>View All...</router-link
>
<div class="items">
<!-- Pass to child component as props: -->
<SubItem :item="categories.items" />
<SubItem :item="categories.items" />
</div>
</div>
Don't bother adding the items to the categories, keep them separate
Instead of multiple computeds, use one computed object hash to store all the filtered sets:
computed: {
filtered() {
if (!this.items) return null;
const filtered = {};
this.items.forEach(item => {
if (filtered[item.category]) {
filtered[item.category].push(item);
} else {
filtered[item.category] = [item];
}
});
return filtered;
}
}
Result:
{
'Glass': [ ... ],
'Ceramic': [ ... ]
...
}
In the template:
<div>
<div v-for="category in categories" :key="category.name">
<div class="items" v-for="item in filtered[category.name]">
<SubItem :item="item" />
</div>
</div>
</div>
You can use a v-if in the parent to prevent displaying anything until the data is loaded:
<display v-if="items" :items="items"></display>
Here is a demo

How to push items into nested array in Vuetify treeview component

I am attempting to setup a Vuetify treeview component to enable the pushing of input into the nested array of existing items. So far, I have been able to set up the component to enable the pushing of input into the nested array of a dynamically added item. However, I want to push new input into the nested array of the existing 'applications' item, instead of creating an entirely new item. How can I achieve this? See my code below. Thanks!
<template>
<v-app data-app>
<v-app-bar app flat>
<v-text-field v-model="newItem" placeholder="add new item"></v-text-field>
<div class="flex-grow-1"></div>
<v-btn color="success" #click="addItem()">
Add Item
</v-btn>
</v-app-bar>
<v-treeview :items="items"></v-treeview>
</v-app>
</template>
<script>
export default {
data: () => ({
newItem: null,
items: [
{
id: 1,
name: 'Applications :',
children: [
{ id: 2, name: 'Calendar : app' },
{ id: 3, name: 'Chrome : app' },
{ id: 4, name: 'Webstorm : app' },
],
},
],
}),
methods: {
addItem () {
this.items.push({
name: 'Applications :',
children: [
{ id: 5, name: this.newItem },
],
})
}
}
}
</script>

Categories

Resources