Tree sauce hope to bring the fun of the front end to you this article has been included github.com/littleTreem... Like star A kind of
Cutting edge: there are a lot of forms in the application of the middle and background. The left hand is a form, and the right hand is a form. There is no end to it. If you use templates to write one by one, it will not only take time and effort to write, but also look like it is too extravagant. So at this time, you will think about whether there is any way to replace the trivial handwritten form template? Let the form be "matched" rather than rolled out, so that you can easily solve the form form, and no longer worry about the form. The answer is: dynamic forms
1. Traditional form template
What does a form need? There is no doubt that the form data collection, verification and submission functions are included. Let's take a look at the form form based on the iview component library
This simple form, if we use handwritten template, the template part is as follows 👇
Data initialization definition and validation submission logic are as follows
The above completes a form with data collection, verification, submission and remaking, but the corresponding problems also come. Here, using templates is not the best choice. The code is too long and there are duplicate codes. If there are more than a dozen forms or more in my project, how much code should I write to maintain such forms will not appear too redundant. Next Enter our leading role today: dynamic form, let's see how to make him "move" 💃 get up
2 dynamic forms
2.1 form I expect
The form I expect can be matched. The form can be dynamically rendered and generated by JSON. The components involved in the form (such as Input and Select) can be rendered by getting the configuration required by JSON. The template rendering mentioned in the previous section is obviously not applicable to this scene. Although vue officially recommends using templates to create your template in most cases, but Some scenes still need render function Official document point me👈
2.2 about rendering functions
Let's take a look at this example, Vue.js To render the VNode node function generated by h() into a real DOM node and mount it on the root node
This h() function is essentially the createElement function. Its function is to generate a VNode node (virtual node), which is not an actual dom element. It's called createnodedescription. We use the information it contains to tell the Vue page what kind of nodes to render, and then use the diff algorithm to track the dom changes
Extension: you may wonder why it's called the h() function instead of the c() for createElement()
h comes from the initial of superscript, the original definition is "create hyper text with JavaScript", while HyperText comes from the familiar html is the abbreviation of HyperText markup language (HyperText Markup Language), so it can be understood that hyper script refers to the script that generates HTML
The createElement function takes three parameters, which are:
- Parameter 1: tag name, option object of component, function, etc. (required);
- Parameter 2: set the style, property, parameter of the passed component and binding event of this object (optional);
- Parameter 3: other nodes under this node, that is, child virtual nodes, can be in the form of strings or arrays, and also need to be built using createElement.
Here is a simple example to illustrate the use of rendering functions 👇
The results of template rendering and rendering function rendering in the above example are the same. Of course, the template is also compiled to get the render function render(), so in fact, the rendering function is more efficient, faster, and reduces the compilation time. For compilation, see this article vue compilation process, compiled from templete to render function
The difference between render function and template
- The performance of render (high) is higher than that of temp (low).
- template is simple, and you can directly see the meaning of the content you want to express, but it is not flexible enough; render render function creates VNode through createElement, which is suitable for developing recurrent components.
After the rendering function, I will introduce the idea of dynamic form
3 implementation of dynamic forms
The dynamic forms based on the iview component library are used here. The components created are all based on the iview. The following is the specific flow chart
3.1 configure form configuration content
I use the example in the first section to configure a form configuration in JSON format (because the configuration file is too long, use text instead)
const formOption = { ref: 'formValidate', style: { //Form style, not required width: '300px', margin: 'auto', }, className: 'form', formProps: { //Not required 'label-width': 80, }, formData: {//The form field data to be monitored must name: '', city: '', sex: 'male', }, formItem: [ //For each formItem of the iview form form, you must { type: 'input', label: 'name', //label corresponding to formItem key: 'name', //key corresponds to the field in formData props: { placeholder: 'Please enter a name', }, rules: { //Form detection rule, not required required: true, message: 'Please fill in the name', trigger: 'blur', }, }, { type: 'select', label: 'city', //label corresponding to formItem key: 'city', //key corresponds to the field in formData props: { placeholder: 'Please enter a name', }, children: [{ label: 'xml', value: '1' }, { label: 'json', value: '2' }, { label: 'hl7', value: '3' } ], rules: { //Form detection rule, not required required: true, message: 'Please select a city', trigger: 'blur', }, }, { type: 'radioGroup', key: 'type', label: 'sex', children: [ { text: 'female', label: 'female', }, { text: 'male', label: 'male', }, ], events: { 'on-change': (vm, value) => { vm.$emit('on-change', value); }, }, } ], events: events('formValidate'),//Form button group } Copy code
There are also corresponding event buttons that are processed uniformly in events (reusable)
3.2 render function render component
The first section involves the form components Input, Select, radioGroup and formItem. They are the render functions that define them
- Exposing APIs for rendering different components
- Input component rendering function
Set the Input API of the iview component library, including props attribute, events event, slot slot, methods and so on, to define the rendering function. The specific implementation is shown in the figure below
function generateInputComponent(h, formData = {}, obj, vm) { const key = obj.key? obj.key : '' let children = [] if (obj.children) { //input has a subset. Let's go here children = obj.children.map(item => { let component if (item.type == 'span') { //Composite input box component = h('span', { slot: item.slot }, [item.text]) } else { let func = componentObj[item.type] component = func? func.call(vm, h, formData, item, vm) : null } return component }) } return h('Input', { props: { value: key? formData[key] : '', ...obj.props }, style: obj.style, on: { ...translateEvents(obj.events, vm), //Time binding input(val) { if (key) { formData[key] = val } } }, slot: obj.slot }, children) } //Event bind function translateEvents(events = {}, vm, formData = {}) { const result = {} for (let event in events) { result[event] = events[event].bind(vm, vm, formData); } return result } Copy code
- Select component rendering function
function generateSelectComponent(h, formData = {}, obj, vm) { const key = obj.key? obj.key : '' let components = [] if (obj.children) { components = obj.children.map(item => { if (item.type == 'optionGroup') { return h('OptionGroup', { props: item.props? item.props : item }, item.children.map(child => { return h('Option', { props: child.props? child.props : child }) })) } else { return h('Option', { props: item.props? item.props : item }) } }) } return h('Select', { props: { value: formData[key], ...obj.props }, style: obj.style, on: { ...translateEvents(obj.events, vm), input(val) { if (key) { formData[key] = val } } }, slot: obj.slot }, components) } Copy code
Here is only the way to realize some components. The main purpose is to sort out the development and application process ideas
- events button generation
function generateEventsComponent(h, formData = {}, obj, vm) { const components = []; if(obj.submit) { const submit = h('Button', { props: obj.submit.props, style: obj.submit.style, class: obj.submit.className, on: { click() { //Verification before submission vm.$refs[obj.ref].validate((valid) => { if (valid) { obj.submit.success.call(vm, formData, vm) } else { obj.submit.fail.call(vm, formData, vm) } }) } } }, [obj.submit.text]) components.push(submit) } if (obj.reset) { const reset = h('Button', { props: obj.reset.props, style: { ...obj.reset.style, }, class: obj.reset.className, on: { click() { vm.$refs[obj.ref].resetFields() //Reset Form obj.reset.success.call(vm, formData, vm); } } }, [obj.reset.text]) components.push(reset) } return h('div',{ class: 'vue-events', style: { ...obj.style } }, components) } Copy code
- The definition of dynamic form component in formBuild
To realize the dynamic generation logic of components, an entry is needed( formBuild.js ), which is to map the corresponding components according to the configuration, generate and merge them into the final form
// form-build.js import componentObj from './utils' export default { props: { options: { type: Object, required: true }, }, render(h) { const options = this.options const formData = options.formData if (!options.formItem) { return h('div') } const components = options.formItem.map(item => { let func = componentObj[item.type] let subComponent = func? func.call(this, h, formData, item, this) : null let component = componentObj.formItem(h, item, subComponent, formData) return componentObj.col(h, item, component) }) const childComp = []; const fromComp = h('Form', { ref: options.ref, style: options.style ? options.style : '', props: { model: formData, ...options.formProps }, class: 'vue-generate-form' }, [ h('Row', { props: options.rowProps }, components) ]); childComp.push(fromComp); if (options.events) { const eventComo = componentObj.events(h, formData, obj.events , vm) childComp.push(eventComp) } return h('div', [childComp]); } } Copy code
You also need to define the plug-in installation of vue
2.3 how to use
- matters needing attention
- Some components (such as button) iview do not provide events like on-click. You can use DOM element native events instead, such as Click
- All form data should be defined in formData
4. Summary
The above can be achieved through the render function. This article mainly introduces the whole development through one way. There are many ways to realize dynamic forms. Of course, you may have doubts
- How to support dynamic form configuration of multiple UI component libraries?
You can refer to how the open source form create (supports three UI frameworks: Iview, ElementUI, ant design Vue) is implemented Form create tool library
- How to develop a dynamic form tool for online editing configuration?
Visual form design tools are also very popular. Interested children's shoes can learn vue-ele-form-generator
Source of ideas: vue-form-make
Previous articles