I have an array of objects inside my Vue instance, and for each item I'd like to write a Computed property.
Each object has only two properties: firstName and lastName. I would like to write a Computed property for each named 'fullName', which is just a concatenation of firstName and lastName.
I'm familiar with implementing Computed properties of data object properties of a Vue instances, but when it comes to doing so with elements of an array, I get confused.
Currently, my code is this:
var app = new Vue({
el: '#app',
data: {
names: [{
firstName: 'Mike',
lastName: 'McDonald',
done: false
},
{
firstName: 'Alex',
lastName: 'Nemeth',
done: false
},
{
firstName: 'Nate',
lastName: 'Kostansek',
done: true
},
{
firstName: 'Ivan',
lastName: 'Wyrsta',
done: true
}
]
},
computed: {
fullName: function(name) {
return name.lastName + ', ' + name.firstName;
}
}
methods: {
toggle: function(name) {
name.done = !name.done;
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<ol>
<li v-for='name in names'>
<input type='checkbox' v-bind:checked='name.done' v-on:change='toggle(name)' />
<span v-if='!name.done'>{{ fullName(name) }}</span>
<del v-else>{{ fullName(name) }}</del>
</li>
</ol>
</div>
And here is the respective jsFiddle
It's recommended to return a function with name as argument from the computed property :
var app = new Vue({
el: '#app',
data: {
names: [{
firstName: 'Mike',
lastName: 'McDonald',
done: false
},
{
firstName: 'Alex',
lastName: 'Nemeth',
done: false
},
{
firstName: 'Nate',
lastName: 'Kostansek',
done: true
},
{
firstName: 'Ivan',
lastName: 'Wyrsta',
done: true
}
]
},
computed: {
fullName(){
return (name)=>name.lastName + ', ' + name.firstName;
},
toggle() {
return (name)=>name.done = !name.done;
}
},
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<ol>
<li v-for='name in names'>
<input type='checkbox' v-bind:checked='name.done' v-on:change='toggle(name)' />
<span v-if='!name.done'>{{ fullName(name) }}</span>
<del v-else>{{ fullName(name) }}</del>
</li>
</ol>
</div>
Another solution is to loop through names array inside a computed property by concatenating firstname and lastname, after that return this array and loop through it in your template
var app = new Vue({
el: '#app',
data: {
names: [{
firstName: 'Mike',
lastName: 'McDonald',
done: false
},
{
firstName: 'Alex',
lastName: 'Nemeth',
done: false
},
{
firstName: 'Nate',
lastName: 'Kostansek',
done: true
},
{
firstName: 'Ivan',
lastName: 'Wyrsta',
done: true
}
]
},
computed: {
fullNames() {
return this.names.map(name => {
let fl = {};
fl.fname = name.firstName + ", " + name.lastName;
fl.done = name.done;
return fl;
})
}
},
methods: {
fullName: function(name) {
return name.lastName + ', ' + name.firstName;
},
toggle: function(name) {
name.done = !name.done;
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<ol>
<li v-for='name in fullNames'>
<input type='checkbox' v-bind:checked='name.done' v-on:change='toggle(name)' />
<span v-if='!name.done'>{{ name.fname }}</span>
<del v-else>{{ name.fname }}</del>
</li>
</ol>
</div>
You can't use the 'computed' with a parameter.
Most probably you want to use a method:
example
<span>{{ fullName('Hi') }}</span>
methods: {
fullName(param) {
return `${this.param} ${this.firstName} ${this.lastName}`
}
}
Related
I have a very basic search filter which allows a user to perform a search for a wine using the wine name and the location of the producer.
However, I would also like the search to work with a list of objects in an array (grapes). Is this possible and how would this be done? My current code is as below.
HTML:
<input type="text" v-model="search" placeholder="Search products">
<ul>
<li v-for="product in filteredProducts">
<h2>{{product.name}}</h2>
<span>{{product.location}}</span>
</li>
</ul>
Then my Vue code is:
new Vue({
el: '#app',
data() {
return {
search:'',
products: [
{
name: 'The Black Sock',
location: 'Australia',
grapes: ['shiraz']
},
{
name: 'Goat Roti',
location: 'France',
grapes: ['Shiraz' , 'Mourvedre']
},
{
name: 'Amon Ra',
location: 'New Zealand',
grapes: ['Chardonnay', 'Riesling']
}
]
}
},
computed: {
filteredProducts:function(){
return this.products.filter((product) => {
return product.name.toLowerCase().match(this.search.toLowerCase()) || product.location.toLowerCase().match(this.search.toLowerCase());
});
}
filteredSearch.sort((a, b) => {
if (a.name< b.name)
return -1;
if (a.name> b.name)
return 1;
return 0;
});
return filteredSearch
},
})
Type in what you want to search no matter if name, location or grapes, if you type in for example shiraz it will display all elements that includes shiraz in name, location or grapes
let vue = new Vue({
el: '#app',
data() {
return {
search:'',
products: [
{
name: 'The Black Sock',
location: 'Australia',
grapes: ['shiraz']
},
{
name: 'Goat Roti',
location: 'France',
grapes: ['Shiraz' , 'Mourvedre']
},
{
name: 'Amon Ra',
location: 'New Zealand',
grapes: ['Chardonnay', 'Riesling']
}
]
}
},
methods: {
sortByName(a,b){
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
},
computed: {
filteredProducts:function(){
let comperator = this.sortByName;
if(this.search == "") return this.products.sort(comperator);
let search = this.search.toLowerCase();
return this.products.filter(({name, location, grapes}) => name.toLowerCase().includes(search) || location.toLowerCase().includes(search) || grapes.find(el => el.toLowerCase().includes(search))).sort(comperator);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="search" placeholder="Search products">
<ul>
<li v-for="product in filteredProducts">
<h2>{{product.name}}</h2>
<p>{{product.location}}</p>
<p>{{product.grapes}}</p>
</li>
</ul>
</div>
You can use includes to check if something exists in the array or not.
You can use filter with includes like this.
const products = [
{
name: 'The Black Sock',
location: 'Australia',
grapes: ['shiraz']
},
{
name: 'Goat Roti',
location: 'France',
grapes: ['Shiraz' , 'Mourvedre']
},
{
name: 'Amon Ra',
location: 'New Zealand',
grapes: ['Chardonnay', 'Riesling']
}
]
const selectedGrapes = 'Shiraz'
const filteredProducts = products.filter(product => product.grapes.includes(selectedGrapes))
console.log(filteredProducts)
I have created for loop which works but for some reason the show/hide part does not work and no matter what I just can't figure out why.
<div id="app">
<ul>
<li v-for="club in clubs" v-on:click="toggleDetails(clubs)">
<h1>{{club.name}}</h1>
<div v-show="clubs.showDetail">
<p>{{club.location}}</p>
<p>{{club.members}}</p>
</div>
</li>
</ul>
</div>
and for the JS part I have the following;
const clubs = [
{
name: 'Tigers',
location: 'Manchester',
members: '22'
},
{
name: 'Dolphins',
location: 'Miami',
members: '19'
},
{
name: 'Bleu Sox',
location: 'Paris',
members: '13'
}
];
const app = new Vue({
el: '#app',
data: {
title: 'Here is a list',
club: clubs
},
methods: {
toggleDetails: function(clubs) {
clubs.showDetail = !clubs.showDetail;
}
}
});
If you need to show detail on each club separately, you need to set a property on each club separately instead of on clubs; Also use Vue.set to reactively add a new property to an object as follows:
const clubs = [
{
name: 'Tigers',
location: 'Manchester',
members: '22'
},
{
name: 'Dolphins',
location: 'Miami',
members: '19'
},
{
name: 'Bleu Sox',
location: 'Paris',
members: '13'
}
];
const app = new Vue({
el: '#app',
data: {
title: 'Here is a list',
clubs
},
methods: {
toggleDetails: function(club) {
this.$set(club, 'showDetails', !club.showDetails)
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.21/dist/vue.js"></script>
<div id="app">
<ul>
<li 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>
If I have code like the code below in my vue.js, upon clicking a button, how can I only display the array item I clicked ( for e.g, Donnie) and hide all of the rest? (Joanne, Peter e.t.c), then when you click the only displayed element again, make all of the other array elements display again?
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
},
computed: {
filteredList() {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
}
},
methods:{
exclude(friend) {
console.log(!friend.name);
},
}
})
HTML
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
You should be able to add an identity check to your filter expression if an item has been clicked.
Start by adding a property to store the clicked friend. I'll call mine selected
data {
selected: null,
keyword: '',
//etc
}
Then in your exclude method
exclude (friend) {
this.selected = this.selected ? null : friend
}
now your computed property can filter the list based on the selected friend first, then fall back to the keyword match
filteredList () {
return this.selected ? [this.selected] : this.friends.filter(friend => {
let search = this.keyword.trim().toLowerCase()
return friend.name.toLowerCase().includes(search) || friend.age.includes(search)
})
}
I think that's what you're looking for:
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
selected: null
},
computed: {
filteredList() {
if (!this.selected) {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
} else {
return [this.selected];
}
},
},
methods:{
exclude(friend) {
if(!this.selected) {
this.selected = friend;
} else {
this.selected = null;
}
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
</div>
The trick here is that the selected data property store the friend and also doubles as a checker if there's a friend, so if not, show all, if is, show only that one.
I'm trying to explore vuex, so what I'm trying to do is to get the count or an array when I remove or add values to it. Below are my codes.
home.vue template
<template>
<div :class="page.class" :id="page.id">
<h3>{{ content }}</h3>
<hr>
<p>Registered users count {{ unRegisteredUserCount }}</p>
<ul class="list-unstyled" v-if="getUnRegisteredUsers">
<li v-for="(unregistereduser, n) in getUnRegisteredUsers" #click="register(unregistereduser)">
{{ n + 1 }}
- {{ unregistereduser.id }}
{{ unregistereduser.fname }}
{{ unregistereduser.lname }}
</li>
</ul>
<hr>
<p>Registered users count {{ registeredUserCount }}</p>
<ul class="list-unstyled">
<li v-for="(registereduser, n) in getRegisteredUsers" #click="unregister(registereduser)">
{{ n + 1 }}
- {{ registereduser.id }}
{{ registereduser.fname }}
{{ registereduser.lname }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'home',
data () {
return {
page: {
class: 'home',
id: 'home'
},
content: 'This is home page'
}
},
computed: {
getUnRegisteredUsers() {
if( this.$store.getters.getCountUnregisteredUsers ) {
return this.$store.getters.getAllUnRegisteredUsers;
}
},
getRegisteredUsers() {
if( this.$store.getters.getCountRegisteredUsers > 0) {
return this.$store.getters.getAllRegisteredUsers;
}
},
unRegisteredUserCount() {
return this.$store.getters.getCountUnregisteredUsers;
},
registeredUserCount() {
return this.$store.getters.getCountRegisteredUsers;
}
},
methods: {
register(unregistereduser) {
this.$store.commit({
type: 'registerUser',
userId: unregistereduser.id
});
},
unregister(registereduser) {
this.$store.commit({
type: 'unRegisterUser',
userId: registereduser.id
});
}
},
mounted: function() {
}
}
</script>
state.js
export default {
unRegisteredUsers: [
{
id: 1001,
fname: 'John',
lname: 'Doe',
state: 'Los Angeles',
registered: false
},
{
id: 2001,
fname: 'Miggs',
lname: 'Ollesen',
state: 'Oklahoma',
registered: false
},
{
id: 3001,
fname: 'Zoe',
lname: 'Mcaddo',
state: 'New York',
registered: false
},
{
id: 4001,
fname: 'Jane',
lname: 'Roberts',
state: 'Philadelphia',
registered: false
},
{
id: 5001,
fname: 'Ellen',
lname: 'Jennings',
state: 'Houston',
registered: false
},
{
id: 6001,
fname: 'Joseph',
lname: 'Reed',
state: 'Boston',
registered: false
},
{
id: 7001,
fname: 'Jake',
lname: 'Doe',
state: 'Portland',
registered: false
}
],
registeredUsers: []
}
getters.js
export default {
getAllUnRegisteredUsers(state) {
return state.unRegisteredUsers;
},
getAllRegisteredUsers(state) {
return state.registeredUsers;
},
getCountUnregisteredUsers(state) {
return state.unRegisteredUsers.length;
},
getCountRegisteredUsers(state) {
return state.registeredUsers.length;
},
getUserById(state) {
}
}
mutations.js
export default {
registerUser(state, payload) {
//find user
const user = _.find(state.unRegisteredUsers, {
'id': payload.userId
});
// remove user from original array
_.remove(state.unRegisteredUsers, {
'id': payload.userId
});
// set user object key value
user.registered = 'true';
// add user to new array
state.registeredUsers.push(user);
console.log(state.registeredUsers.length + ' - registered users count');
},
unRegisterUser(state, payload) {
//find user
const user = _.find(state.registeredUsers, {
'id': payload.userId
});
// remove user from original array
_.remove(state.registeredUsers, {
'id': payload.userId
});
// set user object key value
user.registered = 'false';
// add user to new array
state.unRegisteredUsers.push(user);
console.log(state.unRegisteredUsers.length + ' - unregistered users count');
}
}
During page load it renders the array count properly, but when I remove value to the registeredUsers and unRegisteredUsers the count is not updating. What am I missing here? Can anyone explain and what should I do to get the proper count? Thanks
The reason this is not working is that you are mutating an array. Never mutate an array. You'll spend hours trying to troubleshoot why reactivity broke.
Replace a value with a new array in order to retain reactivity. Use _.filter or _.reject, like the example below.
state.registeredUsers = _.reject(state.registeredUsers, {
'id': payload.userId
});
The other answer by choasia is incorrect. Lodash is not the problem. Lodash is very helpful with Vuejs, you just need to use the functions that explicitly return a new array. See the Lodash docs under "returns" to know what it returns.
To add to For the Name's comments on removing stuff from an array, Use Vue.set when updating//adding to an array.
updateItem(state, payload) {
Vue.set(state.items, payload.index, payload.data);
}
See the documentation here: https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules
Modifying a list or an object in vuejs (as well as vuex) is tricky due to the limitation of JavaScript.
It seems that you are using lodash to remove items in an array. It will cause conflicts with vuejs's reactivity. See issue here.
If you're going to remove an item in an array, you'd better use splice to do so.
From the component I want to access methods of the parent.
(Without props)
Here's the HTML:
<div id="el">
<user v-for="user in users" :item="user"></user>
</div>
Here's the Vue code:
var usersData = [
{ id:1, firstname:'John', lastname: 'Doe' },
{ id:2, firstname:'Martin', lastname: 'Bust' }
];
var vm = new Vue({
el: '#el',
data: { users: usersData },
methods: {
getFullName: function (user) {
return user.id + '. ' + user.firstname + ' ' + user.lastname;
}
},
components: {
user: {
template: '<span>{{ fullName }}</span>',
props: ['item'],
computed: {
fullName: function(){
return this.$parent.getFullName(this.item);
}
},
}
}
});
VueJS version: 2.0.2
Both are this.$parent.$options.methods.getFullName() and this.$parent.methods.getFullName() not working.
For Vue.js version 2.2.0+ you can use dependency injection with inject and provide like this:
HTML:
<div id="el">
<user v-for="user in users" :item="user"></user>
</div>
JavaScript:
var usersData = [
{ id:1, firstname:'John', lastname: 'Doe' },
{ id:2, firstname:'Martin', lastname: 'Bust' }
];
var vm = new Vue({
el: '#el',
data: { users: usersData },
methods: {
getFullName: function (user) {
return user.id + '. ' + user.firstname + ' ' + user.lastname;
}
},
provide: function () {
return {
getFullName: this.getFullName
}
},
components: {
user: {
template: '<span>{{ fullName }}</span>',
inject: ['getFullName'],
props: ['item'],
computed: {
fullName: function(){
return this.getFullName(this.item);
}
},
}
}
});
Fiddle: https://jsfiddle.net/haaiee/v1ahoytm/2/