-- The cover was taken at a guesthouse on Jeju Island

Regular forms

If we use the UI framework as a management system, we are familiar with the form code, which looks like this, for example, this is a comprehensive form in the iView framework:

<template>
    <Form :model="formItem" :label-width="80">
        <FormItem label="Input">
            <Input v-model="formItem.input" placeholder="Enter something..."></Input>
        </FormItem>
        <FormItem label="Select">
            <Select v-model="formItem.select">
                <Option value="beijing">New York</Option>
                <Option value="shanghai">London</Option>
                <Option value="shenzhen">Sydney</Option>
            </Select>
        </FormItem>
        <FormItem label="Radio">
            <RadioGroup v-model="formItem.radio">
                <Radio label="male">Male</Radio>
                <Radio label="female">Female</Radio>
            </RadioGroup>
        </FormItem>
    </Form>
</template>
<script>
    export default {
        data () {
            return {
                formItem: {
                    input: ' ',
                    select: ' ',
                    radio: 'male'
                }
            }
        }
    }
</script>
Copy the code

Configuration form

And the way I want it is this:

The template

<template slot="modalContent">
    <AutoForm
        :fileds="projectFields"
        :model="projectFormData"
        :formName="projectFormData"
        class="my-form"
    />
</template>
<script>
// @ is an alias to /src
import { mapState } from 'vuex'
import { projectFields } from '@/utils/fieldsMap'
export default {
    data ()  {
        return{// Form configuration list projectFields: projectFields}}, computed: {... MapState ({/ / project list page edit form projectFormData: state = > state. The project. The projectFormData})},} < / script >Copy the code

I will use Vuex state to manage the data source of form items:

data

import { projectFormData } from '@/api/project'Const state = {// Edit projectFormData: {projectInput:' ',
        projectSelect: ' ',
        projectRadio: ' '// open mutations open mutations = {// open mutations open mutations = {// open mutations open mutations = { }export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}
Copy the code

The configuration of form items is also managed in a single file (fieldsmap.js) for easy maintenance:

Form item Configuration

// Form configuration items // Note: tag andtypeIt needs to be matched according to the UI framework used. const projectFields = { projectInput: { label:'project Input',
        tag: 'Input'.type: 'text',
        placeholder: 'Please enter project Input'
    },
    projectSelect: {
        label: 'Project drop down Select',
        tag: 'Select',
        options: [
          {
            key: 'beijing',
            value: 'beijing'
          },
          {
            key: 'hangzhou',
            value: 'hangzhou'
          }
        ]
    },
    projectRadio: {
        label: 'project Radio',
        tag: 'RadioGroup',
        options: [
          {
            label: 'is'
          },
          {
            label: 'no'}]}}Copy the code

OK, the entire file structure of a configuration form is used in this way, which can be summarized as a trilogy:

  • The introduction of<AutoForm />Components.
  • fieldsMap.jsTo configure form items, including label, Type, tag, and options.
  • Vuex stateTo add data sources.

The remaining key is how the
component is configured, which is essentially the process of dynamically generating form items (from the configuration file), or in the case of iView, dynamically generating formItems to form a complete form. At this point we need to use the Render Api provided by Vue.

First take a look at the official render screenshot:

render

Simple usage of the three arguments:

<script>
    Vue.component('Line', {
        render: function(h) {
            h('div', {props: {} // pass data},'Text or child node')
        }
    })
</script>
Copy the code

Now that we know the basics, let’s look at the implementation of the
component:

Before we start, let’s look at the structure of the iView Form. From the outer layer to the inner layer, the Form container is fixed — the number of formItems is dynamic — the Input type is dynamic. The component ultimately returns a Form; According to the number of configuration items to determine the number of formItems, dynamic creation; The type of the form is determined by the tag and type of the configuration item. Of course, some form items, such as Select, have options drop-down options and need to be generated separately.

Based on the above analysis, the summary about the
component is There are roughly FormRender, itemsRender, componentUse, type (InputRender, RadioRender, SelectRender), options (optionsRender) five points.

AutoForm.vue

<script>
export default {
  name: 'Form',
  functional: true,
  render (h, context) {
    letFileds = context.props. Fileds // Form configuration from fieldsmap.jsletModel = context.props. Model // Form data from stateletFormName = context.props. FormName /** * render FormItem */function itemsRender () {
      letKeys (fileds).foreach ((ele, I) => {res.push(h(ele, I))'FormItem', { props: { label: Fileds [ele].label // FormItem Label attribute}}, componentUse(Fileds [ele], ele))}returnRes} /** * Form distribution selection * @param {Object} _item - Current fields configuration item * @param {String} _model - Current configuration item name */function componentUse (_item, _model) {
      let typeMap = {
        'Input': InputRender,
        'RadioGroup': RadioRender,
        'Select': SelectRender
      }
      let component = typeMap[_item.tag](_item, _model)

      return [component]
    }

    // Input
    function InputRender (_item, _model) {
      return h('Input',
        {
          props: {
            'v-model': `${formName}.${_model}`,
            'placeholder': _item.placeholder,
            'type': _item.type}, on: {// Methods provided by the iView component to implement bidirectional data binding'on-blur': (e) => {
              model[_model] = e.target.value
            }
          }
        }
      )
    }

    // Radio
    function RadioRender (_item, _model) {
      return h('RadioGroup',
        {
          props: {
            'v-model': `${formName}.${_model}`
          },
          on: {
            'on-change': (e) => {
              model[_model] = e === 'is' ? 1 : 0
            }
          }
        },
        _item.options ? optionsRender(_item.options, 'Radio') : []
      )
    }

    // Select
    function SelectRender (_item, _model) {
      return h('Select',
        {
          props: {
            'v-model': `${formName}.${_model}`
          },
          on: {
            'on-change': (e) => {
              model[_model] = e
            }
          }
        },
        _item.options ? optionsRender(_item.options, 'Option'OptionsRender // Radio // Selectfunction optionsRender (_options, _tag) {
      let itemRes = []
      _options.forEach((_option, i) => {
        if (_tag === 'Radio') {
          itemRes.push(
            h(_tag,
              {
                props: {
                  'label': _option.label
                }
              }
            )
          )
        } else if (_tag === 'Option') {
          itemRes.push(
            h(_tag,
              {
                props: {
                  'key': _option.key,
                  'value': _option.value
                }
              }
            )
          )
        }
      })

      return itemRes
    }

    let items = itemsRender(h)
    return h(
      'Form',
      {
        class: context.data.staticStyle,
        style: context.data.staticStyle,
        props: context.props
      },
      items
    )
  }
}
</script>

Copy the code

Well, with the paving above, you can use the configuration form on any page of your project, so that you don’t have to copy the structure code repeatedly, making the code on the page look clean; More importantly, the way of file management is conducive to maintenance. Actually paging lists can also refer to this way.

A file with a paged list and a base form might look like this:

<template>
  <div class="hc-project-management">
    <CommonList
      :addSearch="addSearch"
      :columns="columns"
      :data="projectList"
      :pageBean="pageBean"
      :statePath="statePath"
    />
    <MyModal
      :isShow="modal.isShow"
      :title="modal.title"
    >
      <template slot="modalContent">
        <AutoForm
          :fileds="projectFields"
          :model="projectFormData"
          :formName="projectFormData"
          class="my-form"/>
      </template>
    </MyModal>
  </div>
</template>
Copy the code

How do I dynamically add form items based on the options in the Select box

Sometimes we have a requirement as described in the title, which automatically adds or changes form items when a drop-down menu is selected.

Implementation: I will monitor data changes in state in watch to add configuration items

Watch: {// Use this syntax to watch'projectFormData': {
      handler: function(val, oldVal) {// Cannot use the arrow function this to point to a problemif (val.projectSelect) {
          this.projectFields = Object.assign({}, this.projectFields, { projectTextarea: {
            label: 'project textarea',
            tag: 'Input'.type: 'textarea',
            placeholder: 'Please type textarea'}})} console.log(val)}, // deep observation deep:true}},Copy the code

Say two words

In fact, both configuration and conventional writing need to be considered comprehensively according to their own business and development members. For example, in configuration, they need to agree on a process and writing method for adding forms with team members, which is relatively fixed and not as free as conventional writing. For example, there are only 2 or 3 forms in this project, is it necessary to configure them? For example, whether the members agree with this writing method also needs to be discussed. But once documented, the maintainability, error-location, and other benefits of configuration seem inexpensive in retrospect.