I'm making a small todolist app using vue. The problem is the next task's checkbox is automatically checked when I click the first checkbox to mark as complete task but it does not happen when I click the last checkbox.
I've tried this on my machine but didn't work. https://jsfiddle.net/razvantirboaca/9aqsjj30/
<div id="app">
<h1>Incomplete Tasks</h1>
<ul>
<li v-for="task in incompleteTasks">
<label>
<input type="checkbox" v-model="task.completed">
{{ task.description }}
</label>
</li>
</ul>
<h1>Complete Tasks</h1>
<ul>
<li v-for="task in completeTasks">
<label>
<input type="checkbox" v-model="task.completed">
{{ task.description }}
</label>
</li>
</ul>
new Vue({
el: '#app',
data: {
tasks: [
{ description: 'First Task', completed: false },
{ description: 'Second Task', completed: false },
{ description: 'Third Task', completed: false },
{ description: 'Fourth Task', completed: false }
]
},
computed: {
incompleteTasks() {
return this.tasks.filter(task => !task.completed);
},
completeTasks() {
return this.tasks.filter(task => task.completed);
}
}
})
Add a key to v-for elements. If there is a unique id you can use that, but since I don't see it in the current data, I have used description as an example
<li v-for="task in incompleteTasks" :key="task.description">
<li v-for="task in completeTasks" :key="task.description">
Related
The goal is to make the output look like this:
<div id="tabs">
<div id="first">
<a>tab 1</a>
<a>tab 2</a>
</div>
<div id="second">
<a>tab 3</a>
</div>
</div>
Currently I'm using this solution (using two v-for loops):
tabs.js (current)
export default {
data() {
return {
tabs: {
first: [{ name: 'tab1' }, { name: 'tab2' }],
second: [{ name: 'tab3' }],
}
}
}
template: `
<div id="tabs">
<div id="first">
<a v-for="tab in tabs.first">{{ tab.name }}</a>
</div>
<div id="second">
<a v-for="tab in tabs.second">{{ tab.name }}</a>
</div>
</div>
`
}
I had an idea to do something like this but it performs more iterations than in the case with two loops:
tabs.js (idea)
export default {
data() {
return {
tabs: {
test: [
{ name: 'tab1', category: 'first' },
{ name: 'tab2', category: 'first' },
{ name: 'tab3', category: 'second' }
]
}
}
}
template: `
<div id="tabs">
<div v-for='category in ["first", "second"]' :id='category' :key='category'>
<template v-for="tab in tabs.test">
<a v-if="tab.category === category">{{ tab.name }}</a>
</template>
</div>
</div>
`
}
I read this topic but it contains slightly different solutions, which unfortunately didn't work in this case.
There's no problem using more than one v-for loops. And there's no problem using nested v-for loops.
The problem I see with your current code is that it's not scalable. You're hard-coding the exact values of your tabs in <template />(e.g: first, second).
The main idea here is to loop through tabs and, inside each tab, to loop through each contents, without the <template> needing to know what the tab is or how many there are.
So that when you change your tabs to, say...
{
tab1: [{ name: 'intro'}],
tab2: [{ name: 'tab2-1' }, { name: 'tab2-2' }],
tab3: [{ name: 'tab3' }]
}
template still works, without needing any change.
To achieve this type of flexibility, you need to use a nested v-for loop:
<div id="tabs">
<div v-for="(items, name) in tabs" :key="name" :id="name">
<a v-for="(item, key) in items" :key="key" v-text="item.name"></a>
</div>
</div>
Demo:
new Vue({
el: '#app',
data: () => ({
tabs: {
tab1: [{
name: 'intro'
}],
tab2: [{
name: 'tab2-1'
}, {
name: 'tab2-2'
}],
tab3: [{
name: 'tab3'
}]
}
})
})
#tabs a { padding: 3px 7px }
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<div id="app">
<div id="tabs">
<div v-for="(links, name) in tabs" :key="name" :id="name">
<a v-for="(link, key) in links"
:key="key"
:href="`#${link.name}`"
v-text="link.name"></a>
</div>
</div>
</div>
But I'd take it one step further and change the tabs to be an array:
data: () => ({
tabs: [
[{ name: 'intro'}],
[{ name: 'tab1' }, { name: 'tab2' }],
[{ name: 'tab3' }]
]
})
And use :id="'tab-' + name" on tab divs if you really need those unique ids. (Hint: you don't).
It makes more sense to me.
I did not see any harm in using two v-for (one for the object keys and another for the array elements) as far as it is all dynamic. You can give a try to this solution by using of Object.keys() :
new Vue({
el: '#app',
data: {
tabs: {
first: [{ name: 'tab1' }, { name: 'tab2' }],
second: [{ name: 'tab3' }],
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="tabs">
<div v-for="tab in Object.keys(tabs)" :key="tab" :id="tab">
<a v-for="tab in tabs[tab]">{{ tab.name }}</a>
</div>
</div>
</div>
You could add a computed property being the .concat from both and loop for it
export default {
data() {
tabs: {
first: [{ name: 'tab1' }, { name: 'tab2' }],
second: [{ name: 'tab3' }],
}
},
computed: {
tabsCombined () {
return this.tabs.first.concat(this.tabs.second)
}
},
template: `
<div id="tabs">
<div v-for='category in tabsCombined' :id='category' :key='category'>
<template v-for="tab in tabs.test">
<a v-if='tab.category === category>{{ tab.name }}</a>
</template>
</div>
</div>
`
}
The problem:
I'm trying to select a radio button by clicking on a list item.
In vuetify manual, this is achieved with a checkbox statically
#9 Action with title and sub-title
My jsfiddle code: https://jsfiddle.net/tgpfhn8m/
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<div id="app">
<template>
<div>
<v-list two-line>
<v-radio-group :mandatory="true" v-model="selectedItem">
<template v-for="(item, index) in items">
<v-list-tile #click="itemType({'name': item.name, 'id':item.id })">
<v-list-tile-action>
<v-radio :value="item.name" :key="item.id"
></v-radio>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ item.name }}</v-list-tile-title>
<v-list-tile-sub-title>{{ item.description }}</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</template>
</v-radio-group>
</v-list>
</div>
</template>
</div>
Vue.use(Vuetify);
const items = [{
id: 1,
name: 'James',
description: "If you don't succeed, dust yourself off and try again.",
},
{
id: 2,
name: 'Fatima',
description: 'Better late than never but never late is better.',
},
{
id: 3,
name: 'Xin',
description: 'Beauty in the struggle, ugliness in the success.',
}
]
var vm = new Vue({
el: '#app',
data: {
items,
selectedItem: '',
},
methods: {
itemType(payload) {
//this.$store.commit('item/setIteminStore', payload)
}
}
})
What is the best way to achieve this dynamically with a radio?
Noting that: Clicking around the radio button selects the radio but not on the list item.
Using vue#2.5.9, vuetify# 0.17.1
you can use onclick to set the selected.
itemType(payload) {
this.selectedItem = payload.name
}
and use watch to store the item for either the user click te radio button or list item the store function is called like this :
watch: {
selectedItem (val) {
console.log(val)
this.$store.commit('item/setIteminStore', payload)
}
},
i have make the fiddle https://jsfiddle.net/zacuwyw3/2/
Sorry, I don't know vuetify. But the solution will be similar.
Below is one pure Vue sample to implement what you need. I think it can be transplanted to vuetify easily.
It is simple, just bind :checked of <input type="radio"> with "team === selectedTeam".
new Vue({
el: '#boxes',
data: {
checked: null,
//checkedNames: [],
teams: [{'text':'team1', 'field':'team1'},{'text':'team2', 'field':'team2'},{'text':'team3', 'field':'team3'}],
selectedTeam: null
},
methods: {
selectOneTeam: function (team) {
this.selectedTeam = team
}
}
})
<div id='boxes'>
<div>
<div>
<p>What you selected:</p>
<a v-for="(team, index) in teams" :key="index">
<span>{{team.text}}:</span>
<input type="radio" name="team" :checked="team === selectedTeam"/>
</a>
</div>
<hr>
<ul>
<li v-for="(team, index) in teams" :key="index" style="width: 100px; float:left" #click="selectOneTeam(team)">
{{team.text}}
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
I am trying to use v-model on v-for loop and its throwing an error.
How can i get this to work
<ul class="">
<li class="" v-model="category.data" v-for="category in categories" :key="category.id">
<input :id="'checkbox'+ category.id" type="checkbox" #change="categoriesComputed($event)" :value="category.slug">
<label :for="'checkbox'+ category.id">
{{category.title | capitalize}}
<span>{{category.job_posts | countObj | toNumber}} Jobs</span>
</label>
</li>
</ul>
And in Vue
<script>
export default {
data(){
return {
type: [],
categories: [],
category: {
data: [],
},
}
},
}
</script>
V-model only works if it’s being used on an input element or a custom component that emits a value event that supplies the value you want v-model to be updated with.
https://v2.vuejs.org/v2/guide/forms.html
https://jsfiddle.net/amcquistan/grq3qj36/
V-model is demo in this fiddle
You have to use v-model in the <input> tags.
const app = new Vue({
el: '#app',
data: {
categories: [
{ id: 1, slug: true, title: 'FOO', job_posts: 'Foo'},
{ id: 2, slug: false, title: 'BAR', job_posts: 'Bar'},
{ id: 3, slug: true, title: 'BAZ', job_posts: 'Baz'}
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<ul class="">
<li class="" v-for="category in categories" :key="category.id">
<input
:id="'checkbox'+ category.id"
type="checkbox"
v-model="category.slug">
<label :for="'checkbox'+ category.id">
{{category.title}} {{category.slug}}
</label>
<input v-model="category.title"/>
</li>
</ul>
</div>
I am trying to create checkboxes that will add an object to an array when checked, but will remove that object when unchecked. I am not sure if this is the correct way, but will show what I have below. Using Angular 2 by the way.
Original Code:
<div>
<ul>
<a *ngFor="let perkResult of perkList.results" (click)="onAddPerk(perkResult)">
<li>{{ perkResult.perk }}</li>
</a>
</ul>
</div>
<div *ngFor="let perk of company.perks;>
{{ perk.perk }}
<a (click)="onDeletePerk(i)"></a>
</div>
Functions:
onAddPerk(perkResult) {
// Adds a new perk to the array
this.company.perks.push({perk: (perkResult.perk)});
}
onDeletePerk(i: number) {
// Delete the perk in the selected index
this.company.perks.splice(i, 1);
}
And I want to do something like this:
<div *ngFor="let benefitResult of benefitList.results" >
<a (click)="onAddBenefit(benefitResult)">
<input type="checkbox" />
//Basically if checked then run add function, if not checked then run delete function
</a> {{ benefitResult.benefit }}
</div>
EDIT:
Got to this point, but cannot reference my other scope for the delete.
<ul *ngIf="benefitList">
<div *ngFor="let benefitResult of benefitList.results; let i = index" >
<input type="checkbox" (change)="updateBenefits($event, benefitResult, i)" >
<li>
{{ benefitResult.benefit }}
</li>
</div>
</ul>
//Original delete
<div *ngFor="let benefit of company.benefits; trackBy: customTrackBy; let i = index">
{{benefit.benefit}}
<a (click)="onDeleteBenefit(i)"></a>
</div>
//function
updateBenefits(event, benefitResult, i) {
if(event.srcElement.checked) {
this.company.benefits.push({benefit: (benefitResult.benefit)});
} else {
this.company.benefits.splice(i, 1);
}
}
You could listen to the change event on the checkboxes. Then pass the event along with the object and index to a method that will look at the status of the checkbox element.
In the component:
optionsList = {
results: [{
perk: 'free coffee',
checked: false
}, {
perk: 'car',
checked: false
}, {
perk: 'pastry',
checked: false
}, {
perk: 'free post-it notes',
checked: false
}]
};
updatePerks(event, perkResult) {
perkResult.checked = event.srcElement.checked;
}
In your HTML template:
<ul>
<li *ngFor="let listItem of optionsList.results">
<label><input type="checkbox" (change)="updatePerks($event, listItem)">
{{ listItem.perk }}</label>
</li>
</ul>
Note: I don't know your data structure, so make the necessary adjustment or post a data sample if you need more help.
I'm sure this one's gonna be extremely easy for you guys. I am trying to make a simple list of posts with the post titles always visible, and when you click a specific post in the list, you get the post's body. I used v-show for that. However, when I click a specific post, the bodies of all of the posts appear, instead of just the one that I clicked.
Here's the template:
<template>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="changeShow">
<h4>{{ post.title }}</h4>
<p v-show="show">{{ post.body }}</p>
<span v-show="show" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
And the logic:
<script>
export default{
data(){
return{
msg:'hello vue',
list: [],
show: false
}
},
ready(){
this.fetchPostList();
},
methods:{
fetchPostList: function () {
var root = 'http://jsonplaceholder.typicode.com';
this.$http.get(root + '/posts').then(function (response) {
this.list = response.data;
})
},
changeShow: function () {
this.show = !this.show;
}
}
}
There's a few ways to approach this depending on your needs.
Multiple Open
You can make each post it's own component, that way you can have show be tied to each individual post instead of all of them.
Vue.component('post', {
template: '#post-template',
props: {
post: Object,
},
data() {
return {
show: false,
}
},
methods: {
toggleShow() {
this.show = !this.show
},
},
})
Then you can have use it like this:
<post v-for="post in posts" :post="post"></post>
One Open
If you just want one open you can pass an id as a prop and show it based on that.
Vue.component('post', {
template: '#post-template',
props: {
post: Object,
selectedId: Number,
},
computed: {
show() {
return this.post.id === this.selectedId
},
},
})
Then you can do like
<post :selected-id="selectedId" :post="post" #click="selectedId = post.id"></post>
I cheated in a different way. Just added a show property to each post and toggled that.
new Vue({
el: 'body',
data: {
list: []
},
ready: function() {
this.fetchPostList()
},
methods: {
fetchPostList: function() {
setTimeout(function() {
this.list.push({
title: 'First Post',
body: 'This is the first Post',
userId: 'Joe',
show: false
});
this.list.push({
title: 'Second Post',
body: 'This is the second Post',
userId: 'Joe',
show: false
});
this.list.push({
title: 'Third Post',
body: 'This is the third Post',
userId: 'Joe',
show: false
});
}.bind(this), 2000);
},
changeShow: function(idx) {
this.list[idx].show = !this.list[idx].show;
}
}
});
<link href="//cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="changeShow($index)">
<h4>{{ post.title }}</h4>
<p v-show="post.show">{{ post.body }}</p>
<span v-show="post.show" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
First of all, the method that I will give an example is not useful, but I am writing to show that there is such a way.
for example
<script>
export default {
data: {
isShow: false,
postUserId: null,
},
};
</script>
<template>
<div class="container">
<h1>My Posts</h1>
<ul class="list-group">
<li class="list-group-item" v-for="post in list">
<div #click="postUserId = post.userId; isShow = !isShow">
<h4>{{ post.title }}</h4>
<p v-show="show">{{ post.body }}</p>
<span v-show="isShow && postUserId == post.userId" class="label label-primary">ID: {{ post.userId }}</span>
</div>
</li>
</ul>
</div>
</template>