preface
Our company is building a background management system recently, which uses Vue and element UI. When we encounter a problem and need to deal with a lot of forms, the solution we think of is to generate dynamic forms through background configuration, which is also a new challenge for me. The functions involved include dynamic form rendering and verification, So let's learn how I realized it!
This article only represents the author's own ideas. If you have a better implementation method, you can leave your valuable suggestions below. The author will be very grateful
Development preparation
Knowledge points to be reserved
- Understanding Element ui forms
- Understand the $set(target,key,value) method in Vue
- Understanding form components in vant
This project is based on vue-cli2 The scaffold built by 0 is built by default here. Who agrees and who opposes!
Static form data preparation
The data returned in the background is like this. Here we take json data as an example
{ "showName": "full name", // name "showValue": null, //value "htmlElements": "Input box", // form types "fieldLength": 99, // Field length "requiredOrNot": 1, // Required }
Then there are the following types
- Input box
- Text field
- calendar control
- Drop down box
- Radio
- check box
We generate a component for each type, test Inside Vue assembly
data(){ return{ fieldArray:[],// Form field collection fieldObj:{}, sex:[{ // Gender name:'male', value:"male" },{ name:"female", value:"female" } ], hobbies:[ // hobby { name:"having dinner", value:"having dinner" },{ name:"play a game", value:"play a game" },{ name:"Beat beans", value:"Beat beans" }, ], job:[{ // occupation name:"doctor", value:"doctor" },{ name:"teacher", value:"teacher" },{ name:"driver", value:"driver" } ] } }
A variety of calendar controls are prepared here because they need to be used when using vant components on subsequent mobile phones
Because the data in vue is bidirectional bound, only the data in data can be bidirectional bound. The data added to data again cannot achieve the effect of bidirectional binding. The official website provides us with a set method.
As a pretty boy, I must have prepared the official website link for you very attentively
There is no more explanation here. The official website is more authoritative. The focus of this blog is dynamic forms.
Vue.set(target,propertyName/index,value)
-
Parameters:
- {Object | Array} target
- {string | number} propertyName/index
- {any} value
-
Return value: the value set.
Usage:
Add a property to the responsive object and ensure that the new property is also responsive and triggers the view update. It must be used to add a new property to a responsive object because Vue cannot detect ordinary new properties (such as this.myObject.newProperty = 'hi')
Note that the object cannot be a Vue instance or the root data object of a Vue instance.
Element UI form element
Dynamic form rendering
Here, axios is used to request local JSON data, static / JSON / form json
{ "data":[ { "showName": "full name", "showValue": null, "htmlElements": "Input box", "fieldLength": 10, "requiredOrNot": 1, "desc":"Please enter your name" }, { "showName": "describe", "showValue": null, "htmlElements": "Text field", "fieldLength": 99, "requiredOrNot": 1, "desc":"Please enter a description" }, { "showName": "hobby", "showValue": null, "htmlElements": "check box", "requiredOrNot": 1, "desc":"Please choose a hobby" }, { "showName": "Gender", "showValue": null, "htmlElements": "Radio ", "requiredOrNot": 1 }, { "showName": "date of birth", "showValue": null, "htmlElements": "calendar control ", "requiredOrNot": 1, "desc":"Please select the date of birth" }, { "showName": "Time of marriage", "showValue": null, "htmlElements": "calendar control ", "requiredOrNot": 1, "desc":"Please choose a wedding time" }, { "showName": "occupation", "showValue": null, "htmlElements": "Drop down box", "requiredOrNot": 1, "desc":"Please select occupation" } ] }
Test.vue file
<template> <div> <h2>Test dynamic form</h2> <el-form :model="fieldObj" ref="ruleForm" label-width="180px" class="demo-ruleForm"> <template v-for="(item,index) of fieldArray"> <template v-if="item.htmlElements==='Input box'"> <el-form-item :label="item.showName"> <el-input v-model="fieldObj[item.showName]" :max="item.fieldLength" :placeholder="item.desc" show-word-limit></el-input> </el-form-item> </template> <template v-if="item.htmlElements==='Text field'"> <el-form-item :label="item.showName"> <el-input type="textarea" rows="4" :placeholder="item.desc" v-model="fieldObj[item.showName]" :maxlength="item.fieldLength" show-word-limit></el-input> </el-form-item> </template> <template v-if="item.htmlElements==='calendar control '"> <el-form-item :prop="item.showName" :label="item.showName"> <el-date-picker v-model="fieldObj[item.showName]" :name="item.showName" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" :placeholder="item.desc" ></el-date-picker> </el-form-item> </template> <template v-if="item.htmlElements==='Drop down box'"> <el-form-item :label="item.showName"> <el-select v-model="fieldObj[item.showName]" :placeholder="item.describe"> <el-option v-for="items in job" :key="items.name" :label="items.name" :value="items.value"> </el-option> </el-select> </el-form-item> </template> <template v-if="item.htmlElements==='Radio '"> <el-form-item :label="item.showName"> <template v-for="(child,index) in sex"> <el-radio v-model="fieldObj[item.showName]" :label="child.value">{{child.name}}</el-radio> </template> </el-form-item> </template> <template v-if="item.htmlElements==='check box'"> <el-form-item :label="item.showName"> <el-checkbox-group v-model="fieldObj[item.showName]"> <template v-for="(child,index) of hobbies"> <el-checkbox :label="child.name"></el-checkbox> </template> </el-checkbox-group> </el-form-item> </template> </template> </el-form> </div> </template> <script> import axios from 'axios' export default { name: "Test", data(){ return{ fieldArray:[],// Form field collection fieldObj:{}, sex:[{ // Gender name:'male', value:"male" },{ name:"female", value:"female" } ], hobbies:[ // hobby { name:"having dinner", value:"having dinner" },{ name:"play a game", value:"play a game" },{ name:"Beat beans", value:"Beat beans" }, ], job:[{ // occupation name:"doctor", value:"doctor" },{ name:"teacher", value:"teacher" },{ name:"driver", value:"driver" } ] } }, mounted(){ this.getFieldData(); }, methods:{ getFieldData(){ // Get dynamic form data axios.get("../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else { this.$set(this.fieldObj,item.showName,item.showValue); } } }) } } } </script> <style scoped> </style>
Now, the forms have been rendered and bidirectional binding has been realized. What needs to be done now is how to realize dynamic form verification.
Explanation on the official website: the Form component provides the function of Form verification. You only need to pass in the agreed verification rules through the rules attribute and set the prop attribute of Form item to the field name to be verified,
- prop field
- rules
- model
Here, the rules are set to be dynamic rather than written in advance in data. Here, we need to know the trigger form of each type
- Input box / text field trigger: 'blur'
- Radio box / check box / calendar control / drop-down box trigger: 'change'
Dynamic form validation
After knowing every form of verification in the form, test The files in Vue become
<template> <div> <h2>Test dynamic form</h2> <el-form :model="fieldObj" ref="ruleForm" label-width="180px" class="demo-ruleForm"> <template v-for="(item,index) of fieldArray"> <template v-if="item.htmlElements==='Input box'"> <el-form-item :label="item.showName" :prop="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please enter'+item.showName, trigger: 'blur' }]:[]"> <el-input v-model="fieldObj[item.showName]" :max="item.fieldLength" :placeholder="item.desc" show-word-limit ></el-input> </el-form-item> </template> <template v-if="item.htmlElements==='Text field'"> <el-form-item :label="item.showName" :prop="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please enter'+item.showName, trigger: 'blur' }]:[]"> <el-input type="textarea" rows="4" :placeholder="item.desc" v-model="fieldObj[item.showName]" :maxlength="item.fieldLength" show-word-limit></el-input> </el-form-item> </template> <template v-if="item.htmlElements==='calendar control '"> <el-form-item :prop="item.showName" :label="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please select'+item.showName, trigger: 'change' }]:[]"> <el-date-picker v-model="fieldObj[item.showName]" :name="item.showName" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd" :placeholder="item.desc" ></el-date-picker> </el-form-item> </template> <template v-if="item.htmlElements==='Drop down box'"> <el-form-item :label="item.showName" :prop="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please select'+item.showName, trigger: 'change' }]:[]"> <el-select v-model="fieldObj[item.showName]" :placeholder="item.describe"> <el-option v-for="items in job" :key="items.name" :label="items.name" :value="items.value"> </el-option> </el-select> </el-form-item> </template> <template v-if="item.htmlElements==='Radio '"> <el-form-item :label="item.showName" :prop="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please select'+item.showName, trigger: 'change' }]:[]"> <template v-for="(child,index) in sex"> <el-radio v-model="fieldObj[item.showName]" :label="child.value">{{child.name}}</el-radio> </template> </el-form-item> </template> <template v-if="item.htmlElements==='check box'"> <el-form-item :label="item.showName" :prop="item.showName" :rules="item.requiredOrNot==1?[{ required: true, message: 'Please select'+item.showName, trigger: 'change' }]:[]"> <el-checkbox-group v-model="fieldObj[item.showName]"> <template v-for="(child,index) of hobbies"> <el-checkbox :label="child.name"></el-checkbox> </template> </el-checkbox-group> </el-form-item> </template> </template> <div class="text-align"> <el-button type="primary" @click="submitForm('ruleForm')">Create now</el-button> <el-button @click="resetForm('ruleForm')">Reset</el-button> </div> </el-form> </div> </template> <script> import axios from 'axios' export default { name: "Test", data(){ return{ fieldArray:[],// Form field collection fieldObj:{}, sex:[{ // Gender name:'male', value:"male" },{ name:"female", value:"female" } ], hobbies:[ // hobby { name:"having dinner", value:"having dinner" },{ name:"play a game", value:"play a game" },{ name:"Beat beans", value:"Beat beans" }, ], job:[{ // occupation name:"doctor", value:"doctor" },{ name:"teacher", value:"teacher" },{ name:"driver", value:"driver" } ] } }, mounted(){ this.getFieldData(); }, methods:{ getFieldData(){ // Get dynamic form data axios.get("../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else { this.$set(this.fieldObj,item.showName,item.showValue); } } }) }, submitForm(formName){ // Submit validation this.$refs[formName].validate((valid) => { if (valid) { console.log('Submit data'); } else { return false; } }); }, resetForm(formName) { // Reset Form this.$refs[formName].resetFields(); } } } </script> <style scoped> </style>
New contents include:
- El form item added: prop = "item.showName"
- El form item added: rules = "item. Requiredornot = = 1? [{required: true, message: 'please select' + item.showName, trigger: 'change'}]: []"
- El form item added: rules = "item. Requiredornot = = 1? [{required: true, message: 'please enter' + item.showName, trigger: 'blur'}]: []"
- Verification methods and methods for resetting forms are added in methods
vant dynamic form validation
Since the pc terminal and the mobile terminal are used together, we also realize the function of dynamic form on the mobile terminal,
Don't talk too much nonsense!
Or take test Vue component as an example, for the mobile terminal, vant dependency needs to be installed first. By default, it has been installed.
Dynamic form rendering
Since there is no drop-down box component on the mobile terminal, instead of using the picker selector, you need to think about how to realize one-to-one correspondence for multiple pickers? What should I do?
form.json adds an object - City, and the previous code can still be reused
{ "showName": "city", "showValue": null, "htmlElements": "Drop down box", "requiredOrNot": 1, "desc":"Please select occupation" }
In this way, there are multiple drop-down boxes to solve the problem of multiple picker s.
Test. Code of Vue
html code area
<template> <div> <h2 class="title">test vant Dynamic form</h2> <van-form @submit="submitClaim"> <template v-for="(item,index) of fieldArray"> <template v-if="item.htmlElements==='Input box'"> <van-field :maxlength="item.fieldLength" show-word-limit v-model="fieldObj[item.showName]" :name="item.showName" :label="item.showName"/> </template> <template v-if="item.htmlElements==='Text field'"> <van-field rows="2" autosize :label="item.showName" :name="item.showName" type="textarea" v-model="fieldObj[item.showName]" :maxlength="item.fieldLength" show-word-limit/> </template> <template v-if="item.htmlElements==='calendar control '"> <van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]" /> </template> <template v-if="item.htmlElements==='check box'"> <van-field :name="item.showName" :label="item.showName"> <template #input> <van-checkbox-group v-model="fieldObj[item.showName]" direction="horizontal"> <template v-for="(child,index) of hobbies"> <van-checkbox :name="child.value">{{child.name}}</van-checkbox> </template> </van-checkbox-group> </template> </van-field> </template> <template v-if="item.htmlElements==='Radio '"> <van-field :name="item.showName" :label="item.showName"> <template #input> <van-radio-group v-model="fieldObj[item.showName]" direction="horizontal"> <template v-for="(child,index) of sex"> <van-radio :name="child.value">{{child.name}}</van-radio> </template> </van-radio-group> </template> </van-field> </template> <template v-if="item.htmlElements==='Drop down box'"> <van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]"/> </template> </template> </van-form> </div> </template>
JavaScript code area
import axios from 'axios' export default { name: "Test", data(){ return{ fieldArray:[],// Form field collection fieldObj:{}, sex:[{ // Gender name:'male', value:"male" },{ name:"female", value:"female" } ], hobbies:[ // hobby { name:"having dinner", value:"having dinner" },{ name:"play a game", value:"play a game" },{ name:"Beat beans", value:"Beat beans" }, ], } }, mounted(){ this.getFieldArray(); }, methods:{ getFieldArray(){ // Get json data of local dynamic form configuration axios.get("../../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else { this.$set(this.fieldObj,item.showName,item.showValue); } } }) }, submitClaim(taskInfo){ } } }
Now, the values of input box, text field, radio box and check box are basically bound in both directions. The next step is to solve the one-to-one correspondence between the values of multiple calendar boxes and drop-down boxes.
The display and hiding of calendar box and pop-up layer are controlled by v-model, so you only need to know the number of calendar box and pop-up layer, and then cycle through the processing, and the getFieldArray method reprocesses it
axios.get("../../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else if(item.htmlElements==='calendar control '){ this.$set(this.dateObj,item.showName,false); // Hide all calendar controls first this.$set(this.fieldObj,item.showName,item.showValue); }else if(item.htmlElements=='Drop down box'){ this.$set(this.fieldObj,item.showName,item.showValue); this.$set(this.dropDownObj,item.showName,false); // Hide all pop-up layers first }else { this.$set(this.fieldObj,item.showName,item.showValue); } } })
Add dateObj object in data
dateObj:{},// Controls the display and hiding of dates
Process calendar control
Add relevant content to html page
<van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]" @click="dateObj[item.showName]=true"/> <template v-for="(item,key,index) of dateObj"> <van-calendar v-model="dateObj[key]" @confirm="(date)=>onConfirmTime(date,item,key)"/> </template>
Methods new methods
onConfirmTime(date,item,key){ // calendar control this.fieldObj[key]=this.formatDate(date); this.dateObj[key]=false; }, formatDate(date) { // format date let year=date.getFullYear(); let month=date.getMonth()+1; let day=date.getDate(); if(month<10){ month='0'+month; } if(day<10){ day='0'+day; } return `${year}-${month}-${day}`; },
Use v-model to bind the date object dateObj written in advance, and then traverse the object to control the display and hiding of each calendar control.
Bind the corresponding key value so that you can get the corresponding value of each calendar object after clicking.
Process drop-down box
Add dropDownObj object, dropDownTempObj object and dropdownmap object in data
dropDownObj:{},// Controls the display and hiding of the drop-down box dropDownTempObj:{},// Drop down box object, which is used for the value in picker dropDownMap:new Map(),
Added in the mounted life cycle
this.dropDownMap.set("occupation",["doctor","teacher","driver"]); this.dropDownMap.set("city",["Beijing","Shanghai","Guangzhou","Shenzhen"])
Add html content to the page
<van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]" @click="dropDownObj[item.showName]=true"/> <template v-for="(item,key,index) of dropDownObj"> <van-popup v-model="dropDownObj[key]" position="bottom" :style="{width: '100%'}"> <van-picker show-toolbar @confirm="(value)=>onConfirmDropdown(value,key)" @cancel="dropDownObj[key]=false" :columns="handleData(dropDownTempObj[key])"/> </van-popup> </template>
Methods add related methods
onConfirmDropdown(value,key){ // Select data from the drop-down box this.dropDownObj[key]=false; this.fieldObj[key]=value; }, handleData(key){ // Drop down box to get each configuration item return this.dropDownMap.get(key); },
getFieldArray method override
axios.get("../../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else if(item.htmlElements==='calendar control '){ this.$set(this.dateObj,item.showName,false); // Hide all calendar controls first this.$set(this.fieldObj,item.showName,item.showValue); }else if(item.htmlElements=='Drop down box'){ this.$set(this.fieldObj,item.showName,item.showValue); this.$set(this.dropDownObj,item.showName,false); // Hide all pop-up layers first this.$set(this.dropDownTempObj,item.showName,item.showName); }else { this.$set(this.fieldObj,item.showName,item.showValue); } } })
Final effect
It can be seen that all the data has achieved two-way binding. The data submitted to the background is the data in the form, which can also be obtained. Finally, what needs to be realized is the verification function of the form.
Dynamic form validation
The verification of input boxes and text fields is relatively simple. You only need to add required and rules verification rules
Input boxes and text fields
<van-field :required="item.requiredOrNot==1?true:false":maxlength="item.fieldLength" show-word-limit v-model="fieldObj[item.showName]" :name="item.showName" :label="item.showName" :rules="[{ required: true, message: 'Please fill in'+item.showName }]"/>
In this way, the verification of input box and text field is basically realized. As for the verification of other form types, the author is still studying
vant dynamic form handles all the code
html code snippet
<van-form @submit="submitClaim"> <template v-for="(item,index) of fieldArray"> <template v-if="item.htmlElements==='Input box'"> <van-field :maxlength="item.fieldLength" show-word-limit v-model="fieldObj[item.showName]" :name="item.showName" :label="item.showName"/> </template> <template v-if="item.htmlElements==='Text field'"> <van-field rows="2" autosize :label="item.showName" :name="item.showName" type="textarea" v-model="fieldObj[item.showName]" :maxlength="item.fieldLength" show-word-limit/> </template> <template v-if="item.htmlElements==='calendar control '"> <van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]" @click="dateObj[item.showName]=true"/> </template> <template v-if="item.htmlElements==='check box'"> <van-field :name="item.showName" :label="item.showName"> <template #input> <van-checkbox-group v-model="fieldObj[item.showName]" direction="horizontal"> <template v-for="(child,index) of hobbies"> <van-checkbox :name="child.value">{{child.name}}</van-checkbox> </template> </van-checkbox-group> </template> </van-field> </template> <template v-if="item.htmlElements==='Radio '"> <van-field :name="item.showName" :label="item.showName"> <template #input> <van-radio-group v-model="fieldObj[item.showName]" direction="horizontal"> <template v-for="(child,index) of sex"> <van-radio :name="child.value">{{child.name}}</van-radio> </template> </van-radio-group> </template> </van-field> </template> <template v-if="item.htmlElements==='Drop down box'"> <van-field :name="item.showName" is-link :label="item.showName" :readonly="true" v-model="fieldObj[item.showName]" @click="dropDownObj[item.showName]=true"/> </template> </template> <van-button type="info" round native-type="submit" :style="{width:'100%',marginTop:'15px'}">Submit</van-button> </van-form> <template v-for="(item,key,index) of dateObj"> <van-calendar v-model="dateObj[key]" @confirm="(date)=>onConfirmTime(date,item,key)"/> </template> <template v-for="(item,key,index) of dropDownObj"> <van-popup v-model="dropDownObj[key]" position="bottom" :style="{width: '100%'}"> <van-picker show-toolbar @confirm="(value)=>onConfirmDropdown(value,key)" @cancel="dropDownObj[key]=false" :columns="handleData(dropDownTempObj[key])"/> </van-popup> </template>
JavaScript code snippet
import axios from 'axios' export default { name: "Test", data(){ return{ fieldArray:[],// Form field collection fieldObj:{}, sex:[{ // Gender name:'male', value:"male" },{ name:"female", value:"female" } ], hobbies:[ // hobby { name:"having dinner", value:"having dinner" },{ name:"play a game", value:"play a game" },{ name:"Beat beans", value:"Beat beans" }, ], dateObj:{ // Controls the display and hiding of dates }, dropDownObj:{ // Controls the display and hiding of the drop-down box }, dropDownTempObj:{ // Drop down box object, which is used for the value in picker }, dropDownMap:new Map(), } }, mounted(){ this.getFieldArray(); this.dropDownMap.set("occupation",["doctor","teacher","driver"]); this.dropDownMap.set("city",["Beijing","Shanghai","Guangzhou","Shenzhen"]) }, methods:{ getFieldArray(){ // Get json data of local dynamic form configuration axios.get("../../static/json/form.json").then(data=>{ let response=data.data.data; this.fieldArray=response; for(let i=0;i<response.length;i++){ let item=response[i]; if(item.htmlElements==='check box'){ this.$set(this.fieldObj,item.showName,[]); }else if(item.htmlElements==='calendar control '){ this.$set(this.dateObj,item.showName,false); // Hide all calendar controls first this.$set(this.fieldObj,item.showName,item.showValue); }else if(item.htmlElements=='Drop down box'){ this.$set(this.fieldObj,item.showName,item.showValue); this.$set(this.dropDownObj,item.showName,false); // Hide all pop-up layers first this.$set(this.dropDownTempObj,item.showName,item.showName); }else { this.$set(this.fieldObj,item.showName,item.showValue); } } }) }, onConfirmTime(date,item,key){ // calendar control this.fieldObj[key]=this.formatDate(date); this.dateObj[key]=false; }, onConfirmDropdown(value,key){ // Select data from the drop-down box this.dropDownObj[key]=false; this.fieldObj[key]=value; }, handleData(key){ // Drop down box to get each configuration item return this.dropDownMap.get(key); }, formatDate(date) { // format date let year=date.getFullYear(); let month=date.getMonth()+1; let day=date.getDate(); if(month<10){ month='0'+month; } if(day<10){ day='0'+day; } return `${year}-${month}-${day}`; }, submitClaim(taskInfo){ console.log(taskInfo); } } }
summary
On the whole, the processing of dynamic forms is not very difficult. What needs to be done is how to process the data. Of course, there are still deficiencies in the processing of file upload, the optimization degree of the code is not done well, and there are a lot of v-if written in v-for. Whether a single component can be used for processing needs to be considered.
ending
If you think this blog is helpful to you, remember to praise the author for three times 👍👍👍, Attention, collection and your support are the biggest driving force in my writing. See you in the next article.