Search code examples
vue.jsvuetify.jsvuelidate

How to use Vuelidate with editable Vuetify Data Table field


I would like to add Vuelidate validation to an editable field in a Vuetify data table. However, I do not know how to make it work with the props.item parameter.

Vuetify Data Table

For a normal input field I would do something like

:error-messages="qtyErrors"
@input="$v.quantity.$touch()"
@blur="$v.quantity.$touch()"
.
.
validations: {
  quantity: { required, numeric }
}

I do not know how to make this work for props.item.squareFootage. I am not sure how to get a handle on the index. Here is my data table. Any suggestions would be appreciated.

<v-data-table
  :headers="bldgHeaders"
  :items="selectedBldgs"
  :pagination.sync="paginationSelected"
  class="elevation-1"
>
  <template slot="items" slot-scope="props">
    <tr>
      <td>{{ props.item.buildingNumber }}</td>
      <td>{{ props.item.description }}</td>
      <td>
        <v-edit-dialog
          :return-value.sync="props.item.squareFootage"
          lazy
          large
        > {{ props.item.squareFootage }}
          <v-text-field
            slot="input"
            v-model="props.item.squareFootage"
            label="Edit"
            single-line
          ></v-text-field>
        </v-edit-dialog>
      </td>
    </tr>
  </template>

Solution

  • You don't want to render a dialog for each row in your datatable. Render one dialog on the page and keep track of which row the user is editing in a data property. This property is called "currentItem" in the below snippet. You can then bind your validations to the properties in just that one object instead of creating validations for every row in the table. If you don't want to use v-dialog you can also use v-menu positioned absolutely without an external activator.

    const {
      required,
      maxLength,
      email
    } = validators
    const validationMixin = vuelidate.validationMixin
    
    Vue.use(vuelidate.default)
    
    new Vue({
      el: '#app',
      data() {
        return {
          editDialog: false,
          currentItem: {},
          headers: [{
              text: 'Dessert (100g serving)',
              align: 'left',
              sortable: false,
              value: 'name'
            },
            {
              text: 'Calories',
              value: 'calories'
            },
            {
              text: 'Fat (g)',
              value: 'fat'
            }
          ],
          desserts: [{
              id: 1,
              name: 'Frozen Yogurt',
              calories: 159,
              fat: 6.0
            },
            {
              id: 2,
              name: 'Ice cream sandwich',
              calories: 237,
              fat: 9.0
            }
          ]
        }
      },
      validations: {
        currentItem: {
          fat: {
            required
          }
        }
      },
      methods: {
        openEditDialog(item) {
          this.currentItem = Object.assign({}, item)
          this.editDialog = true
        },
        validate() {
          this.$v.currentItem.fat.$touch()
          if (!this.$v.currentItem.fat.$error) this.editDialog = false
        }
      }
    })
    <script src="https://unpkg.com/vuelidate/dist/validators.min.js"></script>
    <script src="https://unpkg.com/vuelidate/dist/vuelidate.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <!DOCTYPE html>
    <html>
    
    <head>
      <link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
      <link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
    </head>
    
    <body>
      <div id="app">
        <v-app>
          <v-content>
            <v-container>
              <v-data-table :headers="headers" :items="desserts" class="elevation-1">
                <template slot="items" slot-scope="props">
                  <tr @click="openEditDialog(props.item)">
                    <td>{{ props.item.name }}</td>
                    <td class="text-xs-center">{{ props.item.calories }}</td>
                    <td class="text-xs-center">{{ props.item.fat }}</td>
                  </tr>
                </template>
              </v-data-table>
            </v-container>
            <v-dialog v-model="editDialog" width="500">
              <v-card>
                <v-card-title class="headline grey lighten-2" primary-title>
                  Set Fat Content
                </v-card-title>
    
                <v-card-text>
                  <v-form>
                    <v-text-field label="Fat" v-model="currentItem.fat" required :error="$v.currentItem.fat.$dirty && $v.currentItem.fat.$error">
    
                    </v-text-field>
                  </v-form>
                </v-card-text>
    
                <v-divider></v-divider>
    
                <v-card-actions>
                  <v-spacer></v-spacer>
                  <v-btn color="primary" flat @click="validate()">
                    Save
                  </v-btn>
                </v-card-actions>
              </v-card>
            </v-dialog>
          </v-content>
        </v-app>
      </div>
    
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
    </body>
    
    </html>