Tree jam hope to bring you the fun of the front end of this article has been included github.com/littleTreem… Like to star ✨

Frontier: the background in the application form demand, left hand a form, the right hand is a form of endless, if use template to write one by one, not only difficult and time-consuming to write, and that which is, so this time you will go to imagine, and that is there any way to replace the trivial handwritten form template way? Make the form “assigned” rather than whipped out, so you can easily solve the form and not worry about the form. The answer: dynamic forms

1. Traditional form templates

What does a form need? Form data collection, validation, submission, etc. Let’s take a look at the following form based on the iView component library


This simple form, if we hand it out with a template, looks something like 👇


The data initialization definition and validation submission logic are as follows

Above to complete a data collection, validation, submit, remade the form, but the corresponding problem is here, here with the template is not the best choice, the code is too long, there is also a duplicate code, if more than a dozen of my project form even more, I how not to write more code to maintain this kind of form, will not appear too redundancy, Next we enter today’s protagonist: dynamic form, let us see how to let him “move” 💃 up

2 Dynamic Forms

2.1 My desired form

The form I expect can be configured, and the corresponding form can be dynamically rendered through JSON. The components involved in the form (such as Input and Select) can be rendered by obtaining the required CONFIGURATION of 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 Temlate in most cases, some scenarios still require the render function at 👈

2.2 About rendering functions

Let’s take a look at this example. The Vue. Js mount function renders the VNode function generated by h() as a real DOM node and mounts it to the root node


The h() function is essentially the createElement function, which simply generates a VNode (virtual node), not an actual DOM element. Called createNodeDescription, do we use the information it contains to tell Vue what nodes to render on the page and diff algorithm to track DOM changes

Extension: You may wonder why it is called h() instead of c() for createElement().

H comes from the initials of Hyperscript, the original definition was “Create HyperText with JavaScript”, HTML is the abbreviation of hyper-text Markup Language (HyperText Markup Language), so it can be understood as Hyperscript refers to the script script that generates HTML

The createElment function takes three arguments:

  • Parameter 1: label name, component option object, function, and so on (mandatory);
  • Parameter 2: Set the object’s style, properties, parameters of the passed component, binding events, and so on (optional).
  • Parameter 3: Other nodes under this node, namely child virtual nodes, can be strings or arrays and need to be built using createElement.

Here is a simple example to illustrate the use of the render function 👇


In the above example template rendering and render function rendering the result is the same, of course, the template is essentially rendering function is obtained by the Compile Compile render (), so actually rendering function more efficient, faster, and reduced the time of compilation, about compilation can look at this article vue compilation process, Compiled by Templete into the render function

The render function differs from the template function

  • Render (high) has higher performance than tempate(low).
  • Template is simple, which can intuitively see the meaning of the content, but it is not flexible enough. The render function creates a VNode using createElement, suitable for developing recurring components.

With rendering functions out of the way, let’s talk about dynamic forms

3. Implementation of dynamic forms

Here is the use of the iView component library based on the dynamic form, create components are based on iView to achieve, the following is the specific flow chart


3.1 Configuring the Form Configure the content

I used the example in the first section to configure a form configuration in JSON format (use text instead because the configuration file is too long)

const formOption = {
  ref: 'formValidate'.Style: {// Form style, not required    width: '300px'.    margin: 'auto'. },  className: 'form'.FormProps: {// Mandatory 'label-width': 80,  }, FormData: {// The form field data to listen on name: ' '. city: ' '. sex: 'male'. }, FormItem: [//iview form for each formItem of the form, mandatory {  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 rules, not required required: true. message: 'Name please'. 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 rules, not required required: true. message: 'Please select 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 the code

And the corresponding event buttons are processed in events (reusable)


3.2 Render functions render components

The first example involves the form components Input, Select, radioGroup, and formItem. Respectively are the render functions that define them

  • Expose apis for rendering different components

  • Input component rendering function

Set the API of the IView component library Input, including props attribute, Events event, slot slot, methods and so on, to define the rendering function, as shown in the figure below

function generateInputComponent(h, formData = {}, obj, vm) {
    const key = obj.key? obj.key : ' '
    let children = []

    if (obj.children) { // Input has subsets, go here
 children = obj.children.map(item= > {  let component  if (item.type == 'span') { // Complex 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) }  The bind / / events function translateEvents(events = {}, vm, formData = {}) {  const result = {}  for (let event in events) {  result[event] = events[event].bind(vm, vm, formData);  }   return result } Copy the 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 the code

Here is only to show the implementation of some components, the main purpose is to comb the process of development and application 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() { // Check before commit 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 the 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 the code
  • FormBuild Dynamic form component definition

To implement dynamic component generation logic, this time needs a portal (formbuild.js), which is to map corresponding components according to the configuration and generate merge, combine 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 the code

You also need to define the plug-in installation for vUE


2.3 How to Use it


  • Matters needing attention
  1. Some components (such as button) are not provided by iView similar toon-clickSuch events. You can use DOM element native events instead, for exampleclick
  2. All formData is defined in formData

4. To summarize

The above can be through the render rendering function to complete the implementation of dynamic form tools, this article is mainly through a way of thinking to introduce the whole development, dynamic forms have a variety of ways to achieve, of course, you may also have doubts

  • How to support dynamic form configuration for multiple UI component libraries?

Consider the open source Form-create library, which supports three UI frameworks :Iview, ElementUI, and Ant-Design-Vue

  • How to develop a dynamic form tool for editing configurations online?

The visual form design tool is also interesting, and the vue-ele-form-generator is a good idea for interested children

Vue-form-make

The articles

  • Talk about front-end loading on demand
  • Front-end form data stuff
  • How to better manage the Api
  • The micro front end thing
  • Front-end engineering stuff
  • Develop tool libraries from 0 to 1
  • Develop simple scaffolding from 0 to 1
  • Front-end operations and deployment