如何将 Vuelidate 与可编辑的 Vuetify 数据 Table 字段一起使用

How to use Vuelidate with editable Vuetify Data Table field

我想将 Vuelidate 验证添加到 Vuetify 数据 table 中的 editable 字段。但是,我不知道如何使用 props.item 参数使其工作。

对于正常的输入字段,我会做类似的事情

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

我不知道如何为 props.item.squareFootage 制作此作品。我不确定如何处理索引。这是我的数据 table。如有任何建议,我们将不胜感激。

<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>

您不想为数据中的每一行呈现一个对话框table。在页面上呈现一个对话框并跟踪用户正在编辑数据 属性 中的哪一行。此 属性 在以下代码段中称为 "currentItem"。然后,您可以将验证绑定到仅那个对象中的属性,而不是为 table 中的每一行创建验证。如果你不想使用 v-dialog 你也可以使用 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>

vuelidate 库支持使用 $iter 关键字的 Collection。您只需要在 $iter 关键字下声明项目的数据结构,vuelidate 将为每个项目构建验证对象。例如,您可以在此处查看 https://vuelidate.js.org/#sub-collections-validation.

但是,问题发生在访问 v-data-table 的行模板中的这些验证对象时。由于生成的验证对象只能由迭代器访问,它不是一个列表,行模板中的代码需要一种方法来访问这些验证对象。在这里,我使用了两个辅助函数:getV() 用于访问验证对象和 getVModel() 验证对象的数据模型。

getIter () {
  return Object.values(this.$v.items.$each.$iter)
},
getV (item) {
  if (!item.vLink) item.vLink = this.getIter().filter(i => i.$model.Id === item.Id)[0]
  return item.vLink
},
getVModel (item) {
  return this.getV(item).$model
},

然后在vue的模板中,行模板的:class可以使用getV()来访问$invalid或$dirty,行模板的v-model可以使用getVModel()来访问数据。

示例如下:

v-data-table(:headers="headers" :items="items" item-key="Id")
  template(v-slot:body="{ items }")
    tbody
      tr(v-for="item in items" :key="item.Id" :class="{ vmodified: !getV(item).$invalid && getV(item).$dirty, verror: getV(item).$invalid }")
        td
          input.table-input(type='text' v-model="getVModel(item).Vendor" @input="onChangeItem(getV(item))")
        td
          input.table-input(type='text' v-model="getVModel(item).Material" @input="onChangeItem(getV(item))")