简体   繁体   中英

Updating an input's bound value when edited

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"));    
    }
  }
});

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.

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);  
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM