# 自定义表单域类型

如果您要查找的输入类型不是内置字段(主要是 HTML5 input 类型)的一部分, 并且您还没有找到添加所需功能的 plugin— 您可以创建自己的表单域类型。

# 序言: 表单域的组合方式

Vue Formate 的一个核心概念是让开发人员能够使用极其简单的 API 和单个组件来组合他们的表单。 为了实现这一目标,在开发自定义表单域时,您应该努力通过创建自己类型的来保持跟已有的 FormulateInput 具有相同的便利性, 确保即使在使用自定义表单域时创建表单和字段的语法也保持一致。

这与许多大量使用作用域插槽作为主要扩展机制的 Vue 库明显不同。Vue Formulate 也对 作用域插槽 有强大的支持, 但这些应该更多地用于偶尔的覆盖而不是整个实用程序的定制。

如果您发现自己对作用域插槽进行了大量复制粘贴,或者包装 FormulateInput 在包装器组件中, 则表明您可以更好地实施此原则的一个明显迹象。 如果这些模式正在渗透到您的代码中,请考虑使用 "插槽组件" 或创建自定义表单域。

# 一个表单域的结构

在深入研究代码之前,让我们从高层次看一下 FormulateInput 组件的结构:

FormulateInput internal structure

# 自定义类型

让我们处理一个自动完成字段。我们的目标是扩展 Vue Formulate 以允许如下所示的新表单域:

<FormulateInput
  type="autocomplete"
  name="user"
  label="Search for a user"
  :options="[
    { value: 1, label: 'Jon Doe'},
    { value: 2, label: 'Jane Roe'},
    { value: 3, label: 'Bob Foe'},
    { value: 4, label: 'Ben Cho'},
  ]"
/>

为此,我们需要编写一个自定义组件来处理“自动完成”表单域逻辑。 每个 type 都被指定为一个组件和一个类型。组件和类型都可以在多个 type

# 什么是体系 ?

体系只是围绕类似表单域的 type 对逻辑和样式规则进行分组的一种有用方法, 但仅仅因为您创建了一个新的表单域 type 并不一定意味着您将创建一个新的体系。事实上,我们的示例自动完成很适合 text 体系。

# 什么是表单域组件?

另一个角度讲,表单域组件就是一个 Vue 组件,它传递了一个 context 对象 prop (context 对象 负责组件的几乎所有功能——熟悉它是个好主意)。 此外,您的自定义表单域可以传递它自定义的 prop 到 slotProps 中. 此自定义组件可用于全新的表单域系统、业务逻辑或自定义 UI 中。

# model

如果你需要字段验证,表单聚合或 Vue Formulate 的其他优势,这是一个必要条件, 表单的值可以通过 context.model 读取,或写入。这是一个绑定到 <FormulateInput /> 根节点的特殊的 getter/setter 属性

# 自定义事件

您的自定义组件也可以使用 context.rootEmit()$emit 在任何其他 Vue 组件上使用的完全相同的方式在 <FormulateInput> 根上发出事件。

查看自动完成的源码

文件: MyFormulateAutocomplete.vue

<template>
  <div
    :class="`formulate-input-element formulate-input-element--${context.type}`"
    :data-type="context.type"
  >
    <input
      type="text"
      v-model="context.model"
      v-bind="context.attributes"
      autocomplete="no"
      @keydown.enter.prevent="context.model = selection.label"
      @keydown.down.prevent="increment"
      @keydown.up.prevent="decrement"
      @blur="context.blurHandler"
    >
    <ul
      v-if="filteredOptions.length"
      class="formulate-input-dropdown"
    >
      <li
        v-for="(option, index) in filteredOptions"
        :key="option.value"
        v-text="option.label"
        :data-is-selected="selection && selection.value === option.value"
        @mouseenter="selectedIndex = index"
        @click="context.model = selection.label"
      />
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    context: {
      type: Object,
      required: true
    },
  },
  data () {
    return {
      selectedIndex: 0
    }
  },
  watch: {
    model () {
      this.selectedIndex = 0
    }
  },
  computed: {
    model () {
      return this.context.model
    },
    selection () {
      if (this.filteredOptions[this.selectedIndex]) {
        return this.filteredOptions[this.selectedIndex]
      }
      return false
    },
    filteredOptions () {
      if (Array.isArray(this.context.options) && this.context.model) {
        const isAlreadySelected = this.context.options.find(option => option.label === this.context.model)
        if (!isAlreadySelected) {
          return this.context.options
            .filter(option => option.label.toLowerCase().includes(this.context.model.toLowerCase()))
        }
      }
      return []
    }
  },
  methods: {
    increment () {
      const length = this.filteredOptions.length
      if (this.selectedIndex + 1 < length) {
        this.selectedIndex++
      } else {
        this.selectedIndex = 0
      }
    },
    decrement () {
      const length = this.filteredOptions.length
      if (this.selectedIndex - 1 >= 0) {
        this.selectedIndex--
      } else {
        this.selectedIndex = length - 1
      }
    }
  }
}
</script>

注意: 在上面的例子中,我们 <template> 用一个包含一些 .formulate-input-element 类的 div 来包裹我们的,这不是必需的,但对于主题作者来说,这是一个保持一致的好习惯。

# 自定义 props

通常,在创建自定义表单域时,您需要传递自定义 prop,例如 url 或去抖动持续时间。 自定义表单域也可以声明他们想直接接收自己的 prop。这是通过在注册期间传递配置参数来完成的。 例如,将 prop 公开给名为 myinput 的自定义表单域:

// Vue Formulate config:
{
  library: {
    myinput: {
      classification: 'myclassification',
      component: 'MyFormulateInput',
      slotProps: {
        component: ['url']
      }
    }
  }
}

# 注册表单域

编写输入组件后,您需要让 Vue Formulate 知道有一个新 type 的表单域并且它有一个自定义组件(和 / 或体系)。 您可以通过扩展 library 全局选项来做到这一点。

import Vue from 'vue'
import VueFormulate from '@braid/vue-formulate'
import MyFormulateAutocomplete from './MyFormulateAutocomplete'

// register your component with Vue
Vue.component('MyFormulateAutocomplete', MyFormulateAutocomplete)

Vue.use(VueFormulate, {
  library: {
    autocomplete: {
      classification: 'text',
      component: 'MyFormulateAutocomplete'
    }
  }
})

一瞬间!您现在已经扩展了 Vue Formulate 以包含自定义表单域类型。