Search code examples
design-patternsvue.jscomponentseditcancellation

Vue.js - Updated or undo pattern with child component


I have a problem with updating the correct object from a child component.

My setup is as following: One ul list with most of the data that I want to present or edit. On one part of the "li", there is a child component to show that part of the data, which is a list of resources connected to that object, and also to handle the push of new resources.

I also want the user to have a button the enable edit mode of the row, and then buttons for Update and Cancel.

The problem I'm facing, is to figure out which object I should edit and then save. What I'm doing now, is trying to copy the data in row into a mutableRow that I'm then using as my model for the input controls, while the actual row data is being shown when the user is not in edit mode. In the update method, I post to the db and updated the row object.

ul
  li(v-for="row in rows")
    p(v-if="!row.editing") {{ row.name }}
    input(v-else, v-model="mutableRow.name")
    resources(:row="row")
    button(v-if="!row.editing") Start edit
    template(v-else)
      button Update
      button Cancel

Is this the correct way to do it and if so, how should I handle the prop being sent to my child component? I've tried fetching the mutableRow via this.$parent.mutableRow, I've tried switching the "resource(:row="row")" with a v-if and send in mutableRow if in edit mode, but then I end up changing both objects from the component some how.

Is there another pattern I could use here?


Solution

  • I think the "mutable row" issue you mention is addressed by passing rows[index] rather than row. In any case, that's what it takes to make .sync work.

    My example below implements what I suggested in the comments: a single component with its own copy of the data and its own control for editing mode. When you start editing, the prop data is copied to the local data. When you click Update, an event is emitted, and the parent (via sync) copies the edited data into the row. If you click cancel, no event is emitted, and editing ends.

    new Vue({
      el: '#app',
      data: {
        rows: [{
          name: "Test",
          thingy: 4,
          resources: [{
            title: "Title",
            price: 5000
          }]
        }]
      },
      components: {
        rowEditor: {
          template: '#row-editor-t',
          props: ['row'],
          data() {
            return {
              editing: false,
              localRow: null
            };
          },
          methods: {
            startEditing() {
              this.editing = true;
              this.localRow = JSON.parse(JSON.stringify(this.row));
            },
            stopEditing() {
              this.editing = false;
            },
            update() {
              this.$emit('update:row', this.localRow);
              this.stopEditing();
            },
            cancel() {
              this.stopEditing();
            }
          }
        }
      }
    });
    <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
    <div id="app">
      <ul>
        <li v-for="row, index in rows" is="row-editor" :row.sync="rows[index]">
        </li>
      </ul>
    </div>
    
    <template id="row-editor-t">
      <li>
        <div v-if="editing">
          <input v-model="localRow.name">
          <div v-for="resource in localRow.resources">
            <input v-model="resource.title"><input v-model="resource.price">
          </div>
        </div>
        <div v-else>
          {{row.name}}
          <div v-for="resource in row.resources">
            {{resource.title}}: {{resource.price}}
          </div>
        </div>
        <div v-if="editing">
          <button type="button" @click="update">Update</button>
          <button type="button" @click="cancel">Cancel</button>
        </div>
        <button v-else @click="startEditing">Start edit</button>
      </li>
    </template>
    
    <!--
    ul
      li(v-for=" row in rows ")
        p(v-if="!row.editing ") {{ row.name }}
        input(v-else, v-model="mutableRow.name ")
        resources(:row="row ")
        button(v-if="!row.editing ") Start edit
        template(v-else)
          button Update
          button Cancel
    -->