bsFormBuilder
bsFormBuilder
Section titled “bsFormBuilder”一个基于 Bootstrap (v4.x) + JQuery 的、拖拽的表单构建工具。
-
推荐一个代码格式化工具:http://www.codeformat.cn
- 1、基于 Bootstrap (v4.x) + JQuery,简单易用
- 2、拖动的 html 组件,支持通过 Json 自定义扩展
- 3、组件的属性面板,支持通过 Json 自定义扩展
- 4、支持导出 html 和 json,然后自己通过 json 来构建 UI 页面
- 5、支持导入 json 到 bsFormBuilder,用来进行二次编辑
- 6、丰富的 API,方便二次开发和扩展
- 7、支持 “模板” 功能,可以选择已有模板进行二次开发
- 8、内置轻量的 html 渲染引擎,速度极快,极好用~~
<div id="builder"></div><script> $('#builder').bsFormBuilder({...});</script>在使用前,需要导入 bootstrap 和 jquery 的相关文件。
<link href="path/bootstrap.min.css" rel="stylesheet"><link href="path/bootstrap-icons.css" rel="stylesheet">
<script src="path/jquery.min.js"></script><script src="path/bootstrap.bundle.min.js"></script>
<!-- 导入 bs-form-builder 依赖--><link href="path/bs-form-builder.min.css" rel="stylesheet"><script src="path/bs-form-builder.min.all.js"></script>通过 $('#builder').bsFormBuilder({options...}); 进行初始化,bsFormBuilder 方法可以传入 options 配置,options 内容如下:
{ //模式: "view" 预览模式, "builder" 构建工具模式,默认值为 builder mode: "builder", templateEngine: null, //支持自定义模板渲染引擎,默认使用 fasty bsFormContainerSelector: ".bsFormContainer", // 设计容器 bsFormContainerFilterSelector: ".bsFormFilter", // 设计容器里,不允许拖动的组件 class
bsFormContainerSortableGroup: "shared", // 配置主容器里的 group 名称, 其实就是Sortable的组
bsFormContainerPlaceHolderSelector: ".bsFormContainer-placeholder", // 设计容器里的提示内容 bsFormPropsSelector: ".bsFormProps", // 面板内容 bsFormPropsTitleSelector: ".bsFormPropsTitle", // 面板标题 bsFormPropsFilter:null, // 属性过滤器,用于对特殊的组件进行属性过滤
bsFormPropsItemAppended:null, //监听 props html 内容被追加的时候会被调用可以是函数类型(builder, prop, data) =》 {},用于拖动组件到容器中的初始化
customBuilderStructure: false, // 自定义容器面板,
onDataChange:null, //监听数据更新(更新之前) onDataChanged:null, //监听数据更新(更新之后) renderEmptyDrags: null,//当左边的拖动按钮分类找不到任何组件时,调用该方法 useComponents:[], //使用哪些组件 unUseComponents:[], //排除哪些组件(不使用哪些组件)
components: '#(CPATH)/admin/template/block/components', //可以传个数组或者url get请求返回的格式要和components的格式一样
//支持自定义渲染方法,或者服务端渲染,就是接受一个请求后台返回一个html字符串渲染到页面上, customRender: '#(CPATH)/admin/template/block/render',
optionsDatasources: null, // 定义数据源,就是给option定义的
//初始化数据,就是容器中元素的位置等..信息,不停的拖动一些位置信息会有变化,通过 //$('#builder').bsFormBuilder().data("bsFormBuilder").exportToJson()将数据保存到后台,下次刷新页面的时候就可以保存状态了 //重点: datas: '#(CPATH)/admin/template/block/datas',可以是url 返回对应格式的json数据 datas: [ { "id":"c4044aefc2", "tag":"select", "label":"下拉菜单", "name":"select_1", "options":[{"text":"选项1","value":"value1"},{"text":"选项2","value":"value2"}], "optionsTitle":"选项" }, { "id":"c4044aefc3", "tag":"checkbox", "label":"复选框", "name":"select_2", "options":[{"text":"选项1","value":"value1"},{"text":"选项2","value":"value2"}], "optionsTitle":"选项" } ],
//操作按钮列表 actionButtons:[], //操作按钮模板 actionButtonTemplate:'', //组件扩展配置,配置的内容可以覆盖掉系统的配置 components: {}, //每个组件的默认属性 defaultProps: [], //属性渲染的 html 模板配置 propTemplates: {}, //初始化回调方法 onInit: function(bsFormBuilder){},}2、方法调用
Section titled “2、方法调用”-
1、通过
$('#builder').bsFormBuilder('methodName',arguments...);方法调用。 -
2、或者可以通过
$('#builder').bsFormBuilder().data('bsFormBuilder')来获取bsFormBuilder实例,然后直接调用其方法。
bsFormBuilder 支持的方法如下:
- init: 初始化,bsFormBuilder 自动调用
- render(data, withActive):通过 data 数据,来渲染出一个 html 内容
- renderDefault(data): 系统内置的默认渲染方法,当 component 未定义自己的 render 方法的时候,使用该方法进行渲染。
- deepCopy(target, withNewElementIdAndId):深度拷贝工具类
- createComponentData(component):通过 component 来创建 data 数据
- genRandomId():生成一个随机的 id
- makeFormItemActive(elementId):设置选择状态
- deleteFormItem(elementId):删除一个 formItem
- copyFormItem(elementId):复制一个 formItem
- getDataByElementId(elementId):通过一个节点 id 获取 data 数据
- removeDataByElementId(elementId):通过节点 id 移除 data 数据
- getParentArrayByElementId(elementId):通过节点 id 获取其所在的 数组
- refreshDataIndex($parentElement):刷新 data 的 index 属性
- refreshPropsPanel():渲染(刷新)属性面板
- renderPropTemplate(prop, data, template):渲染属性模板
exportToJson():导出 json 数据(重点常用)- exportToHtml():导出 html 数据
- getDatas():获取 datas 数据,并可以对其进行修改
- addDataToRoot(data):添加一个 data 到根节点
- addDatasToRoot(array):添加一个 data 数组到根节点
- addDataToContainer(data, containerElementId, index):添加一个 data 到一个子container
- addDatasToContainer(array, containerElementId, index):添加一个 data 数组到一个子container
- updateDataAttr(data, attr, value):更新一个 data 的属性,并同步到 html 显示
- refreshDataElement(data):刷新 data 数据到 html
- isViewMode():是否是视图模式
- isBuilderMode():是否是构建模式(构建工具)
- clear():清空设计的所有内容,然后可以重新设计
- destroy():销毁整个组件
3、生命周期
Section titled “3、生命周期”1、刷新右侧的属性面板:refreshPropsPanel
当动态添加右侧自定义输入组件的时候可以在这里初始化
2、clear():清空设计的所有内容,然后可以重新设计
3、destroy():销毁整个组件
3、组件扩展
Section titled “3、组件扩展”在 bsFormBuilder 中,组件是通过一个 json 内容来定义的,一个完整的组件的 json 内容 如下:
components: [ { "name": "灵活栅格", //全局唯一的id "tag": "sgrid", //拖动的元素属性,名称 图标\位置索引..., 这里的type 是表示将拖动元素渲染到那个区域 <-- <div class="tab-content"> <div class="tab-pane fade show active" id="component" role="tabpanel" aria-labelledby="component-tab" > <div class="bsFormDrags-title">模板自带</div> <div class="bsFormDrags d-flex align-items-center" data-type="custom">{"type": "custom"会展示到这}</div> <div class="bsFormDrags-title">表单组件</div> <div class="bsFormDrags d-flex align-items-center" data-type="base"></div> <div class="bsFormDrags-title">辅助组件</div> <div class="bsFormDrags d-flex align-items-center" data-type="assist"></div> <div class="bsFormDrags-title">布局组件</div> <div class="bsFormDrags d-flex align-items-center" data-type="container"></div> </div> <div class="tab-pane fade" id="template" role="tabpanel" aria-labelledby="module-tab"> <div id="bs-template-item-list" class="bs-template-item-list"></div> </div> </div>
--> "drag": { "title": "灵活栅格", "type": "custom", "index": 100, "icon": "bi bi-grid-1x2" }, {<!--自定义选项的数量-->} optionsCounter: 2, {<!--是否带options配置,可以将 props 和 option分开来记,属性是组件带的,选项是自己编辑的,是两个区域-->} withOptions: true, {<!--这里是配置右侧模板选项的,可以根据输入的值自定义数据-->} optionsTitle: '栅格配置', {<!--datasource/custom 如果是custom 使用的是defaultOptions的数据,如果是datasource使用的就是optionsDatasources: []、定义的数据-->} optionsTypes: 'custom', //options 类型值支持自定义一种方式 opiontsAdd: function () { return { text: "栅格" + (++this.optionsCounter), value: 12 } }, defaultOptions: [ { text: "栅格1", value: 6 }, { text: "栅格2", value: 6 } ], "template": '<div class="bsFormItem">' + ' <div class="form-group clearfix">' + ' <div class="row pdlr-15">' + ' {{~for (var i = 0;i<options.length;i++)}}' + ' <div class="col-{{options[i].value}} bsItemContainer">{{$children[i]}}</div>' + ' {{~end}}' + ' </div>' + ' </div>' + '</div>', "onPropChange": function (bsFormBuilder, data, propName, value) {
if (propName !== "options" || !data.children) { return false; }
//修改顺序的时候,保证顺序下的 data 跟着修改 var idDataMapping = {};
let oldOptions = data.options; let newOptions = value;
for (let i = 0; i < oldOptions.length; i++) { let oldOption = oldOptions[i]; idDataMapping[oldOption.elementId] = data.children[i]; }
for (let i = 0; i < newOptions.length; i++) { let newOption = newOptions[i]; data.children[i] = idDataMapping[newOption.elementId]; } } } ],3.1、组件属性描述
Section titled “3.1、组件属性描述”-
name:组件名称
-
tag: 组件 tag,全局唯一,若用户定义的组件 tag 与系统一样,则会覆盖系统的组件定义。
重点:覆盖的只是相同的字段
{"name": "灵活栅格","drag": {"type": "layout","index": 0,"icon": "bi bi-grid-1x2","title": "灵活栅格"},"tag": "sgrid"},//灵活栅格{"name": "灵活栅格","tag": "sgrid","drag": {"title": "灵活栅格","type": "container","index": 100,"icon": "bi bi-grid-1x2"},//这后面的字段还在不会覆盖optionsCounter: 2,withOptions: true,optionsTitle: '栅格配置',optionsTypes: 'custom', //就是自己定义options的json数据(注意这里是数据,并不是数据格式)的意思,还有一个是datasource 就是使用后台返回数据opiontsAdd: function () {return {text: "栅格" + (++this.optionsCounter),value: 12}},//这里的数据格式,默认只能是{text,value}如果需要修改格式的话,需要改源码defaultOptions: [{text: "栅格1",value: 6},{text: "栅格2",value: 6}],"template": '<div class="bsFormItem">' +' <div class="form-group clearfix">' +' <div class="row pdlr-15">' +' {{~for (var i = 0;i<options.length;i++)}}' +' <div class="col-{{options[i].value}} bsItemContainer">{{$children[i]}}</div>' +' {{~end}}' +' </div>' +' </div>' +'</div>',"onPropChange": function (bsFormBuilder, data, propName, value) {if (propName !== "options" || !data.children) {return false;}//修改顺序的时候,保证顺序下的 data 跟着修改var idDataMapping = {};let oldOptions = data.options;let newOptions = value;for (let i = 0; i < oldOptions.length; i++) {let oldOption = oldOptions[i];idDataMapping[oldOption.elementId] = data.children[i];}for (let i = 0; i < newOptions.length; i++) {let newOption = newOptions[i];data.children[i] = idDataMapping[newOption.elementId];}}}, -
drag:左侧显示的模块图标
"drag": {//模块标题"title": "日期和时间",//该模块在那个分类"type": "base",//位置索引"index": 100,//https://icons.getbootstrap.com 对应的类名图标"icon": "bi bi-calendar2-day"}, -
props:组件支持的属性配置
-
propsfilter:系统属性过滤配置,若为配置则显示系统存在的 props 定义
-
withOptions:该属性是否带有 options 配置
-
defaultOptions:options 的默认配置值
-
optionsTypes:options 支持的类型,目前系统内置两种类型: 自定义(custom) 和 数据源(datasource)
-
optionsTitle:options 属性面板的标题
-
template:模板,可以是一个 string 字符串,也可以是一个返回一个 string 的 function(component, data)。
-
onAdd:当组件被添加到 html 的时候回调,或者被拖动的时候,注意:当组件从一个子容器被拖动到另一个子容器,也会调用此方法。
-
onPropChange:当属性被修改的时候,回调。
注意:默认情况下,无需配置 onAdd、onPropChange 方法。除非您已经了解的其作用。
3.2、props 属性
Section titled “3.2、props 属性”bsFormBuilder 已经内置了 4 个属性:tag、id、name、label,任何组件都包含有这 4 个属性(除非定义了 propsfilter),然而,一个复杂的组件除了这 4 个属性以外,还应该有其他的属性,比如 textarea 应该有行数 rows。
所以,textarea 组件的定义如下:
//定义好右侧要展示的模板var defaultPropTemplates = { //number是类型,组件会选择所需要的模板 number: function () { return '<div class="form-group clearfix">' + ' <div class="form-label-left">' + ' {{~ if(required)}}' + ' <span class="red required">*</span>' + ' {{~end}}' + ' <label for="{{id}}">{{label}}</label>' + ' </div>' + ' <div class="flex-auto">' + ' <input id="{{id}}" {{~if (disabled)}}disabled{{~end}} type="text" data-attr="{{name}}" placeholder="{{placeholder}}" class="onkeyup form-control" value="{{value}}">' + ' </div>' + ' </div>'; },
//组件{ "name": "多行输入框", "tag": "textarea", //组件需要的属性 "props": [ { //这里name是自定属性字段变量名,在template中 {{rwos}} 获取变量 name: "rows", //当前属性变量的赋值,要用那种模板,通过propTemplates选项定义 type: "number", label: "行数", placeholder: "请输入行数...", defaultValue: 3, disabled: false, required: true, }, // 可以设置多个字段 { name: "rows", //这里要指定右侧展示属性的模板类型,通过propTemplates选项定义 type: "number", label: "行数", placeholder: "请输入行数...", defaultValue: 3, disabled: false, required: true, } ], "template":"...."}3.3、props 属性描述
Section titled “3.3、props 属性描述”props 是一个组件的属性配置,系统内置了 4 个属性,我们可以通过这个来定义组件自己的属性。
例如,textarea 需要定义行号属性,因此,需要添加如下的配置:
{ name: "rows", type: "input", label: "行数", placeholder: "请输入行数...", defaultValue: 5, disabled: false, required: true,}textarea 定义了名称为 rows 的属性,template 必须通过 {{rows}} 来接收该属性的设置:
<textarea rows="{{rows}}">{{value}}</textarea>- name: 属性名称
- type: 属性渲染类型,支持有:
input/select/number/switch/checkbox/radio/none,可以扩展其他属性类型,或者复写这些属性的默认行为。 - label: 属性在面板里的label
- placeholder: 属性里 placeholder 内容
- defaultValue: 属性的默认值
- disabled: 属性面板里是否启用
- required: 属性面板里是否必填
3.4、template 语法
Section titled “3.4、template 语法”- 输出:{{attr}}- for循环:{{~ for(let item of array)}} -{{item.name}}- {{~end}}- if循环:{{~ if( x === "string")}} -{{x}}- {{~end}}- if-elseif-else循环:{{~ if( x === "string")}} - {{~elseif(x === "other")} - {{~else}} - {{~end}}3.5、template 内置变量:
Section titled “3.5、template 内置变量:”- $builder : bsFormBuilder 实例
- $component:component 实例
- $data:当前 component 的数据
- $children:html 数组,若当前是一个容器,那么该容器下的 html 内容。
4、属性扩展类型
Section titled “4、属性扩展类型”理论上,属性面板支持 input、select、number、switch、checkbox、radio 这 6 种属性类型,已经够用了,不过 bsFormBuilder 依然支持通过在初始化的时候,通过初始化函数来扩展自己的属性面板设置类型。
属性扩展如下:
1. 默认属性defaultProps
Section titled “1. 默认属性defaultProps”var options = { //这里的属性模板其实就是右侧展示的 有可能和option分不太清,个 propTemplates: { otherType:function (){ return '<div> </div>' }, target: function () { return '<div class="form-group clearfix">' + ' <div class="form-label-left">' + ' <legend class="col-form-label pt-0">打开方式</legend>' + ' </div>' + ' <div class="flex-auto">' + ' <select class="custom-select onchange" {{~if (disabled)}}disabled{{~end}} data-attr="{{name}}">' + ' <option value="">当前窗口</option>' + ' <option value="_blank" {{value == "_blank" ? "selected" : ""}}>新窗口(_blank)</option>' + ' </select>' + ' </div>' + ' </div>'; },
image: function () { return '<div class="form-group clearfix">' + ' <div class="form-label-left">' + ' {{~ if(required)}}' + ' <span class="red required">*</span>' + ' {{~end}}' + ' <label for="{{id}}">{{label}}</label>' + ' </div>' + ' <div class="flex-auto">' + ' <div class="jpress-image-browser">' + ' <input type="hidden" class="onchange" id="{{id}}" data-attr="{{name}}" value="{{value}}" />' + ' {{~ if (value)}}' + ' <img src="{{value}}" />' + ' {{~else}}' + ' <img src="#(CPATH)/static/commons/img/nothumbnail.jpg" />' + ' {{~end}}' + ' <div class="image-action">' + ' <a class="image-delete"> <i class="fa fa-trash"></i></a>' + ' <a class="image-edit"> <i class="fa fa-edit"></i></a>' + ' </div>' + ' </div>' + ' </div>' + ' </div>'; },
video: function () { return '<div class="form-group clearfix">' + ' <div class="form-label-left">' + ' {{~ if(required)}}' + ' <span class="red required">*</span>' + ' {{~end}}' + ' <label for="{{id}}">{{label}}</label>' + ' </div>' + ' <div class="flex-auto">' + ' <div class="btn-group ">' + ' {{~ if($data.vid && $data.poster)}}' + ' <a id="group-all" href="javascript:;" class="btn btn-default chooseVideo active"> <i class="bi bi-camera-video"></i> 已选择</a>' + ' <a id="group-28d" href="javascript:;" class="btn btn-default deleteVideo">删除</a>' + ' {{~ else}}' + ' <a id="group-all" href="javascript:;" class="btn btn-default chooseVideo"> <i class="bi bi-camera-video"></i> 选择视频</a>' + ' {{~ end}}' + ' </div>' + ' </div>' + ' </div>'; }, }, defaultProps:[ { name: "属性名称", type: "otherType", label: "属性label", placeholder: "请输入行数..." } ]}$('#builder').bsFormBuilder(options)4. components 中的 props
Section titled “4. components 中的 props”var options = { propTemplates: { otherType:function (){ return '<div> </div>' } }, components:{ component1 :{ "name": "自定义组件1", "tag": "component1", "props": [ { name: "username", type: "otherType", label: "用户名", } ], "template":`<div>{{username}}</div>` }, component2 :{ "name": "自定义组件2", "tag": "component2", "props": [ { name: "属性名称2", type: "otherType", label: "属性名称2", } ], "template":"...." } }}$('#builder').bsFormBuilder(options)5、Options 就是定义选项的
Section titled “5、Options 就是定义选项的”在很多场景下,一些组件(比如复选框、下拉菜单等)的内容不是写死的,也不是自定义的,而是来之某个 API 接口,或者说来源于某个 “数据源”。此时,我们要做一下配置:
- 简单来说,当某些 模块的选项需要动态定制的时候,就需要options了
5.1、定制option
Section titled “5.1、定制option”- 注意:
options只有一个模板类型,要使用多个的话,需要修改源码
-
要修改模板类型,将
optionsTypes作为类型,只后options获取模板的方式//找到refreshPropsPanel 方法,在获取options模板的地方修改//let template = this._getPropTemplateByType("options");let template = this._getPropTemplateByType(this.currentData.component.optionsTypes ?? "options"); -
修改
options的格式//找到同步方法,//*注意:当自定义格式的时候,input修改之后,不自动更新 props中的options选项,就要手动调用同步方法/*** 同步 currentData 的 options* @private*/_syncCurrentDataOptionsFromPropSetting: function () {var options = [];var optionItems = this.$propsPanel.children(".options").children(".option-item");optionItems.each(function (index, item) {//之后修改保存数据的格式var title = $(item).find(".option-input.title").val();var desc = $(item).find(".option-input.desc").val();var href = $(item).find(".option-input.href").val();var elementId = $(item).attr("id");options.push({title, desc, href, elementId});});this.updateDataAttr(this.currentData, "options", options);},
5.2、custom
Section titled “5.2、custom”5.3、数据源
Section titled “5.3、数据源”1、在初始化 bsFormBuilder 的时候,定义好数据源列表,例如:
var options = { optionsDatasources:[ { text: "数据源1", value: "ds1", options: [ {text: "aaa1", value: "value"} ] }, { text: "数据源2", value: 'ds2', options: [ {text: "bbb1", value: "value1"}, {text: "bbb2", value: "value2"} ] }, { text: "数据源3", value: "ds3", options: function (){ return ["..."] } }, { text: "数据源4", value: "ds4", options: "http://www.***.com/***.json" } ]}
$('#builder').bsFormBuilder(options);数据结构说明:
- 1、
optionsDatasources里有多个数据源,可以配置为 array、function、url (String类型) - 2、数据源有 3 个字段: text / value / options
- 3、每个数据源通过 options 字段来定义数据集合
- 4、数据(option)是由 value 和 text 组成的
PS:
- 1、optionsDatasources 可以配置为一个 url 地址(要求返回 json,json 内容必须有 datasources 字段来描述数据源数据)。
- 2、数据源里的 options 字段,可以是一个数据集合,也可以是一个 function,或者一个 url 地址 (要求返回 json,json 内容必须有 options 字段来描述数据)。
当配置 optionsDatasources 字段后,属性面板会多出一个 “选项类型” 的下拉菜单,用户让用户选择自定义选项,还是使用数据源。
1、下载代码
git clone https://gitee.com/fuhai/bsFormBuilder.git2、安装依赖
npm installPS:在安装依赖的过程中,可能会出现网络错误,请配置好网络环境,或者多安装几次…
3、构建编译生成 dist 文件
npm run build