Form-item component packaging details (component library series ~ 4)

Foreword

Today we will discuss the form form component. The open source form form components on the market are basically written in the same way. A form component needs to write a lot of form-item components inside. Embedded select, radio, checkbox, etc. These need to write loops, and you need to write a bunch of class during layout. A slightly larger form component can easily have 100-200 lines, or even more many.
Writing in this way is not only inconvenient to maintain, but also easy to dizzy, which obviously does not match the concept of Vue’s data-driven view. Therefore, we repackage the form-item component. Why not repackage the form form? Read on.

Problem Analysis

  • When there are too many page elements, the amount of form-item code becomes larger, which is not conducive to maintenance.
  • When the data type of the form-item sub-item is list, the writing method is not convenient, and various v-for and keys need to be used in HTML.
  • When you need to use four-column equal-width arrangement layout, you need to write the corresponding col class in each position.
  • When the length of the label field changes, you need to manually adjust the width of the label-width.
  • In order to maintain consistent width and styling of embedded elements, heavy use of CSS styles is required.
  • Configuring form validation rules is a cumbersome process.
  • When the entire form needs to be completely disabled or all have clearable attributes, it is more troublesome to write.
  • The cost of implementing dynamic display/hide of some form-items is high, and the code readability is poor.

Solution ideas

Let me first talk about why the form-item component is extracted instead of the form component.

Optimize the use of form

  • Validation method: Form form validation can be performed through the this.$refs.form.validate method. If the form is separated, the validation will be more complicated and difficult.
  • Property extraction: The form form rarely has content that can be extracted, and its parameters are basically written on the label, and the coding cost is low. If it is embedded in the component, it may cause form and form- The item parameter conflicts.
  • Strong inclusiveness: The internal situation of the form is relatively complicated, so it needs to be more inclusive. If it is embedded inside the component, the component will be too restrictive and the coupling will increase.

Get the code structure by traversing

We see this code snippet:

<el-form-item label="name" prop="name">
 <el-input v-model="form.name" placeholder="Please enter the name"></el-input>
</el-form-item>
<el-form-item label="gender prop="sex">
<el-select v-model="form.sex" placeholder="Please select gender">
<el-option label="Male" value="1"></el-option>
<el-option label="Female" value="0"></el-option>
</el-select>
</el-form-item>

Optimize duplicate code ideas

According to the principle that repeated code can be optimized, we can establish the following ideas:

  1. Observe the n form-items in the form, and find that each item is wrapped in el-form-item;
  2. Consider setting the entire form-item as an array, and each array item corresponds to a form-item;
  3. Each form-item contains label and prop, so we can set the following initial parameters:
props: {<!-- -->
config: {<!-- -->type: Array, default: () => [
{<!-- -->label: "name", prop: "name"},
{<!-- -->label: "gender", prop: "sex"}
]}
}

We can iterate over the page

<el-form-item
v-for="(item, index) in config"
:key="'form-item' + index"
:label="item.label"
:prop="item.prop">
</el-form-item>

We can get:

<el-form-item label="name" prop="name"></el-form-item>
<el-form-item label="gender prop="sex"></el-form-item>

Next, we noticed that the name is an input box (input), gender is a drop-down box (select), and there may be radio buttons (radio ), checkbox (checkbox), time picker (date-picker), datetime picker and time period picker (daterangepicker ) and other subcomponents. Faced with this situation, we can add corresponding parameters in config, I named it itemType (originally wanted to name it type, but due to type is too common to cause conflicts). Therefore, the current data entry parameters are:

props: {<!-- -->
form: {<!-- --> type: Object, required: true, default: () => {<!-- -->} }, // shared form value object passed in
config: {<!-- -->type: Array, default: () => [
// The optional itemType value is the possible high-frequency component name
// input\select\radio\checkbox\date-picker, etc. (the components need to be written clearly, and need to add mandatory verification in require)
{<!-- -->itemType: "input", label: "name", prop: "name"},
{<!-- -->itemType: "select", label: "gender", prop: "sex"}
]}
}

Next, we will encounter a problem when developing the page. How do we put the corresponding form components (input, select, etc.) into it? The simplest and rude way is v-if v-else-if v-else , the disadvantage of this method is that each sub-content may have different logic, including dom will have a big difference, putting them all on one page will lead to a huge number of lines of code on a page, and it is extremely difficult to maintain, which does not conform to the requirements of a single file. The code principle of more than 200 lines (students are interested in this point, you can write a detailed version in the comment area @我, after the component library series is completed). So what we use here is to extract all the sub-components, introduce them through import, and inject components locally. Finally, different components are rendered in the form of components +: is. The code and structure picture are as follows:

<el-form-item
v-for="(item, index) in config"
:key="'form-item' + index"
:label="item.label"
:prop="item.prop">
<components
:is="'item-' + item.itemType || 'text'"
:form="form"
v-bind="{ ...item, noLabel }"
/>
</el-form-item>

In components, form needs to be passed in for two-way binding of subcomponent values, and other parameters in the corresponding item of config also need to be passed in, for example, select requires multiple selections, then the config of the corresponding item: {itemType: ” select”, label: “gender”, prop: “sex”, multiple: true}, and then transparently transmit data through v-bind=”$attrs” at the sub-component layer to achieve compatibility with all elementUI primitives The purpose of the component function

Exception handling

Handle form items that do not match: use Slot

In business development, various strange situations often occur in form items. What if our child components cannot meet these requirements? At this time, you need to use a strong business solution – Slot.

When using Slot, we must first clarify:

  • Which components require sockets
  • How to make sure the socket is inserted correctly into the component
  • How to get the corresponding data in the slot

On the basis of solving the above problems, we can efficiently and flexibly deal with the situation that the form items do not meet the requirements. It is worth noting that our basic component library should contain as little business logic as possible.

The thinking is clear, the problem is solved, let’s code happily together!

1. Which one needs the slot?

We can add an optional parameter “isSlot” with a value of Boolean in config, the situation is as follows:

props: {<!-- -->
   form: {<!-- --> type: Object, required: true, default: () => {<!-- -->} }, // shared form value object passed in
   config: {<!-- -->type: Array, default: () => [
   // The optional itemType value is the possible high-frequency component name
   // input\select\radio\checkbox\date-picker, etc. (the components need to be written clearly, and need to add mandatory verification in require)
   {<!-- -->itemType: "input", label: "name", prop: "name"},
   {<!-- -->itemType: "select", label: "gender", prop: "sex"},
   {<!-- -->itemType: "select", label: "ID Card Slot", prop: "idCard", isSlot: true}
   ]}
}

2. How to ensure that the embedded slot is placed in the specified position

When traversing, if the current parameter item.isSlot is false or does not exist, then we use components is
If it exists, we use the slot tag to specify the location of the slot content by means of a named slot. The name of the named slot uses the value of item.prop, and the result is as follows:

<el-form-item
v-for="(item, index) in config"
:key="'form-item' + index"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
v-bind="{ ...item, noLabel }"
/>
<slot v-else :name="item.prop" />
</el-form-item>

3. How to get the data of the corresponding item in the slot
We can pass data in and out of slots through scoped named slots. code show as below

<el-form-item
v-for="(item, index) in config"
:key="'form-item' + index"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
v-bind="{ ...item, noLabel }"
/>
<slot v-else v-bind="item" :name="item.prop" />
</el-form-item>

In this way, our form-item component supports the insertion of subcomponents and the compatibility of abnormal business so far.

Finally, post the final code and effect diagram of the form-item component (due to security issues, the fields have been deleted and the prop value has been modified):

 <el-form ref="form" :model="form" :rules="rules">
    <hc-form-item :form="form" :rules="rules" :config="formConfig" :column="3" clearable same-label class="mt-20\ ">
        <com-upload slot="certPositive" v-model="form.certPositive" ext=".jpg,.jpeg,.png,.gif" />
    </hc-form-item>
</el-form>
computed: {<!-- -->
formConfig() {<!-- -->
       return [
           {<!-- --> itemType: 'input', label: 'ID card number', prop: 'aa', readonly: this.type !== 'save' },
           {<!-- --> itemType: 'input', label: 'name', prop: 'bb', readonly: this.type !== 'save' },
           {<!-- --> itemType: 'radio', label: 'gender', prop: 'cc', code: 'staff_extension_sex' },
           {<!-- --> itemType: 'date-picker', type: 'date', label: 'date of birth', prop: 'dd', 'value-format' : 'yyyy-MM-dd', disabled: true },
           {<!-- --> itemType: 'input', label: 'Contact Number', prop: 'ee' },
           {<!-- --> itemType: 'input', label: 'mailbox', prop: 'ff' },
           {<!-- --> itemType: 'select', label: 'native place', prop: 'hh', code: 'native_place' },
           {<!-- --> itemType: 'select', label: 'education/education level', prop: 'oo', code: 'staff_extension_education' },
           {<!-- --> itemType: 'date-picker', type: 'date', label: 'date of residence permit processing', prop: 'residentPermitDate', 'value-format ': 'yyyy-MM-dd' },
           {<!-- --> itemType: 'input', label: 'certificate front photo', prop: 'certPositive', isSlot: true },
       ];
   }
}
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">


PS: We will gradually optimize the code packaging of form-item to solve the eight problems raised before. At the same time, a blog with related content is being produced, so stay tuned.

Component library code address

https://gitee.com/yangxiongasin/hc-basic

Component library document code address

https://gitee.com/yangxiongasin/hc-docs

If you are interested in component library development, you can enter the QQ group: 617330944 to discuss and exchange with everyone