概述
jformer 是一个动态表单呈现组件,仅通过 json 数据就可以动态生成界面,jformer 具有扩展性,能够自定义渲染处理方式,在渲染时控制输出的界面呈现形式
json-schema 是一种描述数据的格式,通常用于数据格式验证、表单验证等方面,现在利用 jformer 的扩展特性实现将json-schema 数据渲染成表单
实现
先定义一个 html 页面,引用 jformer,示例采用 elementui 组件库
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Json Schema 示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/jformer"></script>
<script src="https://cdn.jsdelivr.net/npm/element-ui/lib/index.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/element-ui/lib/theme-chalk/index.css"
/>
</head>
<body>
<div id="app"><j-former v-bind:config="config"></j-former></div>
<script>
new Vue({
data() {
return {
config: {
fields: []
}
}
}
}).$mount('#app')
</script>
</body>
</html>
准备一个 json-schema 数据
{
type: 'object',
required: ['lastName', 'firstName'],
properties: {
lastName: { type: 'string', description: '姓' },
firstName: { type: 'string', description: '名' },
age: { type: 'number', description: '年龄' },
subobj: {
type: 'object',
properties: {
text1: { type: 'string', description: '子属性1' },
text2: { type: 'string', description: '子属性2' }
}
}
}
}
要想实现通过 json-schema 数据动态生成表单,需要定义一个渲染处理,在 jformer 准备渲染界面之前改变当前节点的数据结构,将 json-schema 数据转换成表单数据格式
// 定义一个函数,实现输出表单项
const buildFieldItem = field => {
field.component = 'el-form-item'
field.fieldOptions = {
props: { label: field.description || field.name }
}
}
// 定义一组字段处理方法,根据不同数据类型输出相应输入组件
const typeproviders = {
// 当数据类型是 object 时,字段输出为表单
object: field => {
field.component = 'el-form' // 定义输出的组件
field.fieldOptions = { props: { labelWidth: '150px' } }
// 当数据类型时 object 时,属性 properties 可作为表单项
// 将 properties 转换成一组属性的描述,赋给 children 属性
// 在渲染 children 的时候,会单独根据 child 的数据类型输出特定表单项
field.children = Object.keys(field.properties).map(key => {
const name = field.parent ? `${field.parent}.${key}` : key
return {
...field.properties[key],
name,
parent: name // 设置 parent 属性,当 child 也是对象时实现级联嵌套
}
})
},
// 当数据类型是字符串时,输出输入组件
string: field => {
// 每个输入组件都嵌套在表单项内
buildFieldItem(field)
field.children = [{ component: 'el-input', model: [field.name] }]
},
// 当数据类型是字符串时,输出数字输入组件
number: field => {
buildFieldItem(field)
field.children = [
{ component: 'el-input-number', model: [field.name] }
]
}
}
// 在 jformer 中注册渲染处理
window.jformer.default.use(({ provider }) => {
provider(() => {
return field => {
;(typeproviders[field.type] || new Function())(field)
}
})
})
关于表单验证,这里举一个是否必填的简单例子,json-schema 中定义属性是否必填是通过 object 类型的节点的 required 属性定义的,这里只需要在处理 object 节点时候将 properties 中属性名和 required 中属性对应上生成 rules 就可实现
必填验证处理
const objectprovider = field => {
field.component = 'el-form'
field.fieldOptions = {
props: { labelWidth: '150px' }
}
field.children = Object.keys(field.properties).map(key => {
const name = field.parent ? `${field.parent}.${key}` : key
return {
...field.properties[key],
name,
// 生成 children 的时候同时生成 rules
rules: [
{
required: !!(field.required || []).find(item => item === key),
message: '必填项'
}
],
parent: name
}
})
}
const buildFieldItem = field => {
field.component = 'el-form-item'
field.fieldOptions = {
props: {
label: field.description || field.name,
prop: field.name,
rules: field.rules // 在处理表单项时候 rules 赋过来
}
}
}
完整示例如下,将以下定义存成 html 就能预览效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Json Schema 示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/jformer"></script>
<script src="https://cdn.jsdelivr.net/npm/element-ui/lib/index.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/element-ui/lib/theme-chalk/index.css"
/>
</head>
<body>
<div id="app">
<j-former v-model="model" v-bind:config="config"></j-former>
<p>数据: {{JSON.stringify(model)}}</p>
</div>
<script>
const buildFieldItem = field => {
field.component = 'el-form-item'
field.fieldOptions = {
props: {
label: field.description || field.name,
prop: field.name,
rules: field.rules
}
}
}
const typeproviders = {
object: field => {
field.component = 'el-form'
field.fieldOptions = {
props: { labelWidth: '150px', model: field.model }
}
field.children = Object.keys(field.properties).map(key => {
const name = field.parent ? `${field.parent}.${key}` : key
return {
...field.properties[key],
name,
rules: [
{
required: !!(field.required || []).find(item => item === key),
message: '必填项'
}
],
parent: name
}
})
},
string: field => {
buildFieldItem(field)
field.children = [{ component: 'el-input', model: [field.name] }]
},
number: field => {
buildFieldItem(field)
field.children = [
{ component: 'el-input-number', model: [field.name] }
]
}
}
window.jformer.default.use(({ provider }) => {
provider(() => {
return field => {
;(typeproviders[field.type] || new Function())(field)
}
})
})
new Vue({
data() {
return {
model: {},
config: {
fields: [
{
type: 'object',
model: '$:model',
required: ['lastName', 'firstName'],
properties: {
lastName: { type: 'string', description: '姓' },
firstName: { type: 'string', description: '名' },
age: { type: 'number', description: '年龄' },
subobj: {
type: 'object',
properties: {
text1: { type: 'string', description: '子属性1' },
text2: { type: 'string', description: '子属性2' }
}
}
}
}
]
}
}
}
}).$mount('#app')
</script>
</body>
</html>
相关链接
数据源、监听、转换表达式具体定义参考 文档