1
votes

first I will explain my problem and what I'm doing. I'm coding a component that represents a single row within a table. That component at first shows information about a category (Its name, description and id). I've an Edit button where I can edit the name and the description of each category. Looks like this: enter image description here

When I click the edit button I replace the data with inputs (only in Name and Description), so I'm able to change the values I then send the information to the server.

So, what's the problem? The problem is that when I press the edit button, then I change the input value (It's bound with v-model) and then I press the Edit button again (so I don't want to change the data) the data actually remains as the input was and I don't want that. I just want the initial value.

Well, I've solved that by using :value instead of v-model (maybe there is another way?).

The thing is that when I save the changes and then I check if the input is correct It re-renders the DOM (when I show an error message) and so the value that has initially the input and I don't want that. Here is the component's code:

<template>
  <tr>
    <th>
      {{idCategory}}
    </th>
    <td>
      <p v-if="isEditingCategory === false">
        {{category.name}}
      </p>
      <form class="field" v-show="isEditingCategory">
        <div class="control">
          <input  class="input"
            type="text"
            :value="category.name"
            ref="categoryNameInput"
            style="width: auto;">
        </div>
        <p class="help is-danger" v-show="showHelpMessage">al menos 3 caracteres.</p>
      </form>
    </td>
    <td>
      <!-- TODO. hacer un pipe para que muestre solo los primeros 10 caracteres. -->
      <p v-if="isEditingCategory === false">
        {{ category.description }}
      </p>
      <form class="field" v-if="isEditingCategory">
        <div class="control">
          <input class="input" type="text" :value="category.description" ref="descriptionInput" style="width: auto;">
        </div>
      </form>
    </td>
    <td>
      <!-- Buttons... -->
      <button class="button is-info is-outlined" @click="onEditCategory">
      <span class="icon">
      <i class="fas fa-edit"></i>
      </span>
      </button>
      <button class="button is-success"
        @click="onSaveCategory"
        v-if="isEditingCategory">
      <span class="icon">
      <i class="far fa-save"></i>
      </span>
      </button>
    </td>
  </tr>
</template>
export default {
  data() {
    return {
      isEditingCategory: false,
      isPostingChanges: false,
      category: {
        id: this.idCategory,
        name: this.categoryName,
        description: this.categoryDescription,
        index: this.arrayIndex
      },
      showHelpMessage: false
    }
  },
  methods: {
    onSaveCategory() {
      this.showHelpMessage = false;
      const MIN_CHAR = 3;
      const categoryNameValue = this.$refs.categoryNameInput.value;
      const descriptionValue = this.$refs.descriptionInput.value;
      if (categoryNameValue.length >= MIN_CHAR) {
        console.log(categoryNameValue)
      } else {
        this.showHelpMessage = true;
      }
    },
    onEditCategory() {
      this.isEditingCategory = !this.isEditingCategory;
    }
  },
  props: ['idCategory', 'categoryName', 'categoryDescription', 'arrayIndex']
}
1

1 Answers

1
votes

The general way to handle a workflow like this is with these steps

  1. On switching to edit mode, copy the prop values to a local copy and bind to your form inputs with v-model
  2. When hitting save, validate the local values
  3. Submit the values to your server. This is also a good time to disable the inputs, buttons, etc
  4. Once successful, emit an event to the component parent with the new values and switch off edit mode
  5. The parent receives this event and updates its data. The changes to the data flow down through your row component's props and the values are updated

For example (just focusing on a single input)

<td>
  <fieldset class="field" :disabled="isPostingChanges" v-if="isEditingCategory">
    <div class="control">
      <input class="input"
        type="text"
        v-model="categoryForm.name"
        style="width: auto;"
      >
    </div>
    <p class="help is-danger" v-show="showHelpMessage">al menos 3 caracteres.</p>
  </fieldset>
  <p v-else>
    {{ category.name }} <!-- 👈 display the prop value here -->
  </p>
</td>
export default {
  props: { category: Object }, // just pass the entire category object as a prop
  data: () => ({
    isEditingCategory: false,
    isPostingChanges: false,
    showHelpMessage: false,
    categoryForm: null
  }),
  methods: {
    onEditCategory () {
      this.isEditingCategory = !this.isEditingCategory
      if (this.isEditingCategory) {
        // assign local copies from props
        this.categoryForm = { ...this.category }
      }
    },
    async onSaveCategory () {
      // do your validation against `this.categoryForm`, then if valid...

      this.isPostingChanges = true

      // now send to your server (this is all guesswork)
      const res = await fetch(`/api/categories/${this.category.id}`, {
        method: "PUT",
        headers: { "Content-type": "application/json" },
        body: JSON.stringify(this.categoryForm)
      })
      this.isPostingChanges = false
    
      if (res.ok) {
        const updated = await res.json() // the updated values from the server
        this.isEditingCategory = false
        this.$emit("updated", updated) // emit the new values
      }
    }
  }
}

Then in your parent component, add a listener

<row-component
  v-for="(category, index) in categories"
  :key="category.id"
  :category="category"
  @updated="updateCategory($event, index)"
/>
methods: {
  updateCategory(category, index) {
    // remove the old category and replace it with the updated one
    this.categories.splice(index, 1, category)
  }
}