JqueryFormBuidler 自定义控件
一、JqueryFormBuilder文件树
源码文件目录
├─demo
│ │ index.html
│ │
│ ├─js
│ │ actionButtons.js
│ │ demo.js
│ │
│ └─sass
│ demo.scss
│
├─fonts
│ config.json
│ formbuilder-icons.woff
│
├─js
│ │ config.js
│ │ control.js
│ │ controls.js
│ │ data.js
│ │ dom.js
│ │ events.js
│ │ form-builder.js
│ │ form-render.js
│ │ helpers.js
│ │ layout.js
│ │ README.md
│ │ test1.js
│ │ testData.txt
│ │ utils.js
│ │
│ ├─control
│ │ autocomplete.js
│ │ button.js
│ │ controlFileList.js
│ │ custom.js
│ │ file.fineuploader.js
│ │ hidden.js
│ │ index.js
│ │ myImage.js
│ │ paragraph.js
│ │ README.md
│ │ select.js
│ │ text.js
│ │ textarea.js
│ │ textarea.quill.js
│ │ textarea.tinymce.js
│ │
│ └─control_plugins
│ README.md
│ starRating.js
│ textarea.trumbowyg.js
│
└─sass
│ form-builder.scss
│ form-render.scss
│ _controls.scss
│ _kc-toggle.scss
│ _stage.scss
│
└─base
_animation.scss
_bs.scss
_font.scss
_mixins.scss
_variables.scss
如果需要实现自定义控件,需要了解以下文件
- js/form-builder.js 设计器的源码,以及绑定的事件,基本都在该文件中
- js/helpers.js 在用户编辑设计器上的控件的属性后会调用updatePreview方法更新预览的视图,所以可以在该方法中加入我们自己的业务逻辑生成想要的规则
- js/utils.js 主要是markup方法,该方法可以帮我们生成页面上的html元素,源码中大量用到了该方法
- 多语言文件,根据自己的语言环境进行设置。在demo/assets/lang文件夹下 。在设置自定义控件后,该自定义控件的名称,属性,都是自己定义的,如果不在多语言文件中提前定义,则在预览视图就看不到自定义控件的名称,属性名
二、自定义控件类型
jqueryFormBuilder中的所有控件都是继承自control类。所以我们在创建的自定义控件的时候也需要继承该类
以自定义控件 图片控件 为例
文件 js/control/controlImg.js
首先根据官方文档
- 引入 control 文件
- 定义一个继承自
control
的类,也可以继承control
的子类 - 定义一个静态的definition方法(这一步不是必须的)。如果你希望你的控件有一个自定义图标以及多语言映射,那么是必须的
- 非必要步骤,定义一个configure方法,该方法可以指定外部的js以及css,如果你有另外的配置,同样可以在该方法中进行配置
- 必要步骤,定义一个build方法,在该方法中可以使用this.config获取默认配置属性,以及自定义的配置属性。该方法需要返回一个Dom元素。可以使用utils文件中的markup方法
- 非必要步骤,定义一个onRender方法,在Dom元素从build方法插入至界面时被调用,会接收一个event参数。
- 调用
control.register
方法注册新的控件类型
参考代码
import control from '../control'
//define a new class extending `control` (or any other child control class)
export default class controlImg extends control {
// define a static get definition class if you wish to have a custom icon
static get definition() {
return {
// mi18n custom mappings (defaults to camelCase type)
mi18n: {
lecImg: 'lecImg'
},
}
}
//define a configure method if required - used to specify external javascript & css files and perform any additional configuration
configure() {
}
//define a `build` method - this is always required
build() {
const { values, value, placeholder, type, inline, other, toggle, ...data } = this.config
name = this.config.multiple ? `${name}[]` : name
const inputConfig = Object.assign({}, this.config, { name })
if(typeof(inputConfig.src) == 'undefined' || inputConfig.src == ''){
//设置一个默认的图片地址
inputConfig.src = "https://gitee.com/justsawa/mypicture/raw/master/image/20201208195552.png"
}
if(inputConfig.width == ''){
//设置默认的图片宽高
inputConfig.width = 100
}
if(inputConfig.height == ''){
//设置默认的图片宽高
inputConfig.height = 100
}
//设置自定义样式
inputConfig.class = 'my_img'
//生成
const htmlTpl = this.markup('img', null, inputConfig)
return htmlTpl
}
onRender() {
}
}
// register this control for the following types & text subtypes
control.register(['controlImg'], controlImg)
在js/control/index.js文件中引入,这样才能在右边的控件列表显示出来
三、默认图片控件-事件触发,预览视图修改
在第二节中,参考官方文档,创建了一个自定义控件,但是,这样只是定义好了自定义控件的类型,在实际开发中,我们可能需要自定义的属性,事件去实现我们自己业务需求。
3.1 配置多语言文件
因为我们这个自定义控件的类型是新建的,并没有在多语言文件中进行设置,那么他在设计界面的控件列表中就显示不了label。在form-builder.js文件中的appendNewField方法中可以看到,label的取值是从多语言文件中取值的,
如果没有则显示不了名称,导致界面显示有问题,如图
JQueryFormBuilder的多语言文件默认使用的是英语。demo/assets/lang/en-US.lang
加入后
3.2 设置属性的编辑框
因为我们加了自定义的属性,src、height、width三个属性,但是这个时候点击编辑框是看不到这三个属性的编辑栏的。
因为JQueryFormBuilder在的设计器类,在向面板添加控件时,会根据控件类型(就是控件注册时写的名称)去寻找这个控件需要的属性然后根据设置为其分配指定的编辑框,最后返回对应的Dom数据。
代码路径 : js/form-builder.js
重点方法介绍
- prepFieldVars 该方法为定义好的控件返回一个标准的设计器数据结构
- appendNewField
- 该方法为编辑器添加一个新的控件。
- 控件会包含2部分
- 一部分是预览部分,class名:prev-holder;
- 另一部分是属性编辑部分,class名:frm-holder
- 参数 :
- values 就是当前的控件,包含了当前的控件的一切属性以及数据
- advFields 该方法为控件构建属性编辑框
- 参数 :
- values 就是当前的控件,包含了当前的控件的一切属性以及数据
- 参数 :
- defaultFieldAttrs 这个方法中,传入控件类型参数,返回对应的属性
在代码中可以看到,appendNewField 方法会返回一个Dom元素,该元素中包含了预览部分和编辑部分。
我们需要在编辑部分加入自定义的属性,让用户在设计表单时可以对其控制
在defaultFieldAttrs 方法中为我们的控件类型,配置属性,因为图片类型是不带value属性的,所以在该方法中需要加上一个过滤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3HYkKvOX-1607672640301)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201209112104786.png)]
然后advFields 方法就能找到这个属性。可以看到该方法中定义了一个advFieldMap的变量。该map的key就是属性名称,而value则是一个一个的箭头函数,返回对应的编辑框。所以,我们要在这个变量中,加入自己的代码才能生成对应的编辑框。
由于src,width,height三个属性都是一般的编辑框。我们可以使用源码中原本定义好的函数
这个时候,再去页面上查看,编辑框就能显示出来了
3.3 修改预览视图
至此,虽然实现了属性的编辑框,但是我们还没有实现编辑后,就能在预览视图中看到。
要实现这个效果,需要知道JQueryFormBuilder的预览视图的更新过程。
- 用户点击关闭按钮,或者edit按钮切换视图时,会触发事件。
- 事件中,会先获取当前元素的控件元素,其实也是一个Dom节点,包含了预览部分和编辑部分,然后将该元素作为参数传入Helper.js中的toggleEdit方法中
- 在toggleEdit方法中会调用Helper.js中的updatePreview方法。这个方法会更新预览视图,传入的参数也是当前的控件所在Dom元素
- 最后可以看到在该方法中调用了this.layout.build方法,这个方法调用的就是我们这个控件定义的时候的build方法。所以updatePreview只是更新了该控件的一些属性,真正的构建视图还是在build方法做的
我们需要在updatePreview中加入自己的业务逻辑,修改业务需要的属性值。
参考代码:
if(fieldType.match('controlImg')){
previewData.src = $(".fld-src", field).val();
previewData.height = $(".fld-height", field).val();
previewData.width = $(".fld-width", field).val();
}
完整代码:
updatePreview($field) {
const _this = this
const d = this.d
const fieldClass = $field.attr('class')
const field = $field[0]
if (fieldClass.includes('input-control')) {
return
}
console.log($field)
const fieldType = $field.attr('type')
const $prevHolder = $('.prev-holder', field)
let previewData = Object.assign({}, _this.getAttrVals(field, previewData), { type: fieldType })
if (fieldType.match(d.optionFieldsRegEx)) {
previewData.values = []
previewData.multiple = $('[name="multiple"]', field).is(':checked')
$('.sortable-options li', field).each(function(i, $option) {
const option = {
selected: $('.option-selected', $option).is(':checked'),
value: $('.option-value', $option).val(),
label: $('.option-label', $option).val(),
}
previewData.values.push(option)
})
}
if(fieldType.match('fileList')){
previewData.values = []
$('.myfile li', field).each(function(i, $option) {
const file = {
label: $(this).children(":first").text(),
value: $(this).children(":first").attr("href")
}
previewData.values.push(file)
})
}
//如果控件类型是controlImg 则修改其属性
if(fieldType.match('controlImg')){
previewData.src = $(".fld-src", field).val();
previewData.height = $(".fld-height", field).val();
previewData.width = $(".fld-width", field).val();
}
previewData = trimObj(previewData, true)
previewData.className = _this.classNames(field, previewData)
$field.data('fieldData', previewData)
// determine the control class for this type, and then process it through the layout engine
const custom = controlCustom.lookup(previewData.type)
const controlClass = custom ? custom.class : control.getClass(previewData.type, previewData.subtype)
const preview = this.layout.build(controlClass, previewData)
empty($prevHolder[0])
$prevHolder[0].appendChild(preview)
preview.dispatchEvent(events.fieldRendered)
}
现在对我们的图片属性编辑框进行编辑,就可以实现实时更新预览了
可以看到图片的宽高已经变成了200 * 200
四、默认图片控件-getData数据获取(自定义属性获取)
到前面几步实现了自定义控件的实时编辑更新,但是获取数据时,却没有办法获取到我们想要的数据
如图:
可以看到并没有我们定义的属性返回,但是编辑之后,其实是有属性的。
如果想要在没有编辑的时候,或者在获取数据时有自己的业务逻辑,可以修改Helper.js中的prepData方法
参考代码
//为controlImg增加指定属性
if(fieldData.type == "controlImg"){
fieldData.src = $(".fld-src", field).val();
fieldData.height = $(".fld-height", field).val();
fieldData.width = $(".fld-width", field).val();
}
这个代码要放在
fieldData = trimObj(fieldData)
这个代码后面,因为trimObj方法中会对空值的属性进行剔除,
完整代码
prepData(form) {
const formData = []
const d = this.d
const _this = this
if (form.childNodes.length !== 0) {
// build data object
forEach(form.childNodes, function(index, field) {
const $field = $(field)
if (!$field.hasClass('disabled-field')) {
let fieldData = _this.getTypes($field)
const $roleInputs = $('.roles-field:checked', field)
const roleVals = $roleInputs.map(index => $roleInputs[index].value).get()
fieldData = Object.assign({}, fieldData, _this.getAttrVals(field))
if (fieldData.subtype) {
if (fieldData.subtype === 'quill') {
const id = `${fieldData.name}-preview`
if (window.fbEditors.quill[id]) {
const instance = window.fbEditors.quill[id].instance
const data = instance.getContents()
fieldData.value = window.JSON.stringify(data.ops)
}
} else if (fieldData.subtype === 'tinymce' && window.tinymce) {
const id = `${fieldData.name}-preview`
if (window.tinymce.editors[id]) {
const editor = window.tinymce.editors[id]
fieldData.value = editor.getContent()
}
}
}
if (roleVals.length) {
fieldData.role = roleVals.join(',')
}
fieldData.className = fieldData.className || fieldData.class
if (fieldData.className) {
const match = /(?:^|\s)btn-(.*?)(?:\s|$)/g.exec(fieldData.className)
if (match) {
fieldData.style = match[1]
}
}
fieldData = trimObj(fieldData)
const multipleField = fieldData.type && fieldData.type.match(d.optionFieldsRegEx)
if (multipleField) {
fieldData.values = _this.fieldOptionData($field)
}
if(fieldData.type == "fileList"){
fieldData.values = _this.fieldFileListData($field)
}
//为controlImg增加指定属性
if(fieldData.type == "controlImg"){
fieldData.src = $(".fld-src", field).val();
fieldData.height = $(".fld-height", field).val();
fieldData.width = $(".fld-width", field).val();
}
formData.push(fieldData)
}
})
}
return formData
}
截图
效果展示