Reputation: 471
I'm working on a basic to-do application. Each to-do/task item gets listed as an input item in a Vue <list-item>
component, and the <list-item>
s are displayed with a v-for pointing to a tasks array.
I'm trying to allow the user to edit each task input, and upon changing the value, have this update the array item (rather than just the input itself). My @change event on the input is firing, but I'm at a loss as to what to do after this point.
https://jsfiddle.net/xbxm7hph/
HTML:
<div class="app">
<div class="add-control-area columns is-mobile is-multiline">
<responsive-container>
<div class="field is-grouped">
<div class="control is-expanded">
<input class="input add-control-text" type="text" placeholder="New Task" v-model="newTask" v-on:keyup.enter="addTask">
</div>
<div class="control">
<a class="button is-white add-control-button" @click="addTask" :disabled="!isThereText">Add Task</a>
</div>
</div>
</responsive-container>
<responsive-container>
<list-item v-for="task, index in tasks" :item="task" :index="index" @task-completed="completeTask(index)" @task-deleted="deleteTask(index)" ></list-item>
</responsive-container>
</div>
</div>
JS:
Vue.component('list-item', {
props: ['item', 'index'],
template: `<div class="task-wrapper">
<input class="task" :value="item" @change="updateTask()">
<div class="task-control delete-task" @click="deleteTask()"></div>
<div class="task-control complete-task" @click="completeTask()"></div>
</div>
`,
methods: {
completeTask: function() {
this.$emit('task-completed', this.index);
},
deleteTask: function() {
this.$emit('task-deleted', this.index);
},
updateTask: function() {
console.log('changed');
}
}
});
Vue.component('responsive-container', {
template: `
<div class="column is-4-desktop is-offset-4-desktop is-10-tablet is-offset-1-tablet is-10-mobile is-offset-1-mobile">
<div class="columns is-mobile">
<div class="column is-12">
<slot></slot>
</div>
</div>
</div>
`
});
var app = new Vue({
el: '.app',
data: {
tasks: [],
completedTasks: [],
newTask: ''
},
methods: {
addTask: function() {
if(this.isThereText) {
this.tasks.push(this.newTask);
this.newTask = '';
this.updateStorage();
}
},
completeTask: function(index) {
this.completedTasks.push(this.tasks[index]);
this.tasks.splice(index, 1);
this.updateStorage();
},
deleteTask: function(index) {
this.tasks.splice(index, 1);
this.updateStorage();
},
updateStorage: function() {
localStorage.setItem("tasks", JSON.stringify(this.tasks));
}
},
computed: {
isThereText: function() {
return this.newTask.trim().length;
}
},
// If there's already tasks stored in localStorage,
// populate the tasks array
mounted: function() {
if (localStorage.getItem("tasks")) {
this.tasks = JSON.parse(localStorage.getItem("tasks"));
}
}
});
Upvotes: 0
Views: 946
Reputation: 55644
Use a v-model
directive on your <list-item>
component, instead of passing in an item
property. You will also need to pass in a reference from the array (tasks[index]
), because task
in this scope is a copy that is not bound to the element of the array:
<list-item v-for="task, index in tasks" v-model="tasks[index]"></list-item>
In your component definition for the list item, you'll need to now take in a value
prop (this is what gets passed when using v-model
) and set a data property item
to that value. Then, emit an input
event on the change to pass the item
value (this is what the component is listening for when using v-model
):
Vue.component('list-item', {
props: ['value'],
template: `<div class="task-wrapper">
<input class="task" v-model="item" @change="updateTask"></div>
</div>
`,
data() {
return {
item: this.value,
}
},
methods: {
updateTask: function() {
this.$emit('input', this.item);
}
}
});
Here's a fiddle with those changes.
As Bert Evans mentioned, even though this works, Vue requires that components using the v-for
directive also use a key
attribute (you will get a warning from Vue otherwise):
<list-item
v-for="task, index in tasks"
:key="index"
v-model="tasks[index]"
></list-item>
Also, realize that the index
variable in a v-for
scope can change, meaning that the item at index 1 might change to index 4 and this can pose some problems as the application gets more complex. A better way would be to store items
as an object with an id
property. This way you can have an immutable id associated with the item.
Upvotes: 1
Reputation: 71
You can pass the index and new value to your change event handler:
<input class="task" :value="item" @change="updateTask(index, $event)">
Then access them accordingly:
updateTask: function(index, event) {
console.log(index);
console.log(event.target.value);
}
Upvotes: 0