集成jsoneditor库,用于编辑复杂配置项:https://github.com/json-editor/json-editor
jsoneditor文档: jsonEditor.README.md
安装:
./tool/jdcloud-plugin.sh add ../jdcloud-plugin-jsonEditor
在逻辑页对话框中使用示例:page/dlgOrderConfRule.html
<tr>
<td>配置值</td>
<td class="wui-jsonEditor" data-options="schema:'schema/example.js'">
<textarea name="value" rows=14></textarea>
<p class="hint">
<a class="easyui-linkbutton btnEdit" data-options="iconCls: 'icon-edit'" href="javascript:;">修改</a>
<a class="easyui-linkbutton btnFormat" data-options="iconCls: 'icon-reload'" href="javascript:;">格式化JSON</a>
<a class="easyui-linkbutton btnEditJson" data-options="iconCls: 'icon-edit'" href="javascript:;">配置</a>
</p>
</td>
</tr>
注意:在data-options中设置schema:'schema/example.js'
,由该文件定义JSON格式。 为了兼容,也支持在配置按钮(CSS类为btnEditJson)上指定schema:
<a class="easyui-linkbutton btnEditJson" data-options="iconCls: 'icon-edit'" data-schema="schema/example.js" href="javascript:;">配置</a>
如果想不显示JSON内容,只显示编辑按钮,这时在data-options中设置input:false
:
<tr>
<td>配置值</td>
<td class="wui-jsonEditor" data-options="schema:'schema/example.js', input:false">
<textarea name="value" rows=14></textarea>
<p class="hint">
<a class="easyui-linkbutton btnEditJson" data-options="iconCls: 'icon-edit'" href="javascript:;">配置</a>
</p>
</td>
</tr>
标识类wui-jsonEditor已被定义为组件,其下面的标识类btnEdit, btnFormat和btnEditJson的三个类自动绑定了相应的操作。 其中btnEditJson则是弹出新的窗口,在该窗口中编辑配置。 配置项的schema定义由data-schema
属性定义,示例见web/schema-example.js。
wui-jsonEditor组件有以下事件:
显示JSON编辑对话框:
DlgJson.show(schemaFile, jsonData, onSetJson, showDlgOpt)
schemaFile是json格式说明文件,习惯放在“web/schema”目录下。
示例1:取数据、编辑并保存:
callSvr("JDConf.get", {name: name}, function (data) {
DlgJson.show("schema/" + name + ".js", data, onSetJson, {modal: false});
});
function onSetJson (data) {
callSvr("JDConf.set", {name: name}, function () {
app_show("更新成功");
}, data);
}
示例2:取数据、编辑并保存。保存前检查是否有变更,无变更则忽略保存。
var initValue = null;
callSvr("UiCfg.getValue", {name: "menu"}, function (data) {
initValue = data;
DlgJson.show("schema/menu.js", initValue, onSetJson, {modal: false});
});
function onSetJson (data) {
var str = JSON.stringify(data, null, 2);
if (str == initValue)
return;
callSvr("UiCfg.setValue", {name: "menu"}, function () {
UiMeta.handleMenu(data);
initValue = str;
app_show("已成功更新");
}, {value: str});
}
也可以直接调用底层的dlgJson对话框,其扩展对话框参数如下:
示例:显示JSON编辑框编辑数据
var arrEditor = this.parent.parent;
var data = arrEditor.getValue();
WUI.showDlg("#dlgJson_inst_colSeq", {
editorOpt: {
schema: colSeqSchema,
startval: data
},
onSetJson: function (data) {
...
arrEditor.setValue(data);
},
});
schema文件一般存为 schema/xx.js,是一个返回js对象的脚本,它比普通JSON文件更灵活。 返回的对象可以是JSONEditor支持的schema格式(参见schema-example), 如:
{
type: "object",
title: "XX配置",
properties: {
name: {
type: "string"
},
title: {
type: "string"
}
}
}
也可以是完整的JSONEditor的options,如:
var schema = {
type: "object",
title: "XX配置",
properties: {
name: {
type: "string"
},
title: {
type: "string"
}
}
}
// 最后返回的JS对象,须用小括号括起来。有schema属性,表示返回的是完整的JSONEditor选项
({
no_additional_properties:true,
schema: schema,
})
常用的JSONEditor选项有:
required: true
)属性,则属性前显示勾选框。也可以设置各级editor选项(可全局设置,也可在schema各级的options中设置),如对象上
在schema各级中常用选项:
show_opt_in=true
),要使用其它属性只能从属性列表中手工勾选。示例:["res", "join", "default"]
本插件默认设置了如下选项(可在schema文件中再覆盖它们):
theme: "spectre",
iconlib: "spectre",
remove_empty_properties: true,
use_default_values: false,
show_opt_in: true
注意:此前版本使用的是bootstrap库,换成了轻量的spectre库,且做了scoped处理(须在.spectre类下才生效),避免影响全局。 在dlgJson.html中引入了CSS库并对主题色做了定制适配。
CSS库文件制作于:git@github.com:skyshore2001/json-editor.git 下面的css目录。
type=boolean
Basic
页中。常见类型的示例参考web/schema-example.js。
关于布局:
grid_columns: (1到12)
和grid_break: true
选项调整布局:{
"type": "object",
"format": "grid-strict",
"properties": {
"a": {
"title": "a",
"type": "string",
"options": {
"grid_columns": 4
}
},
"b": {
"title": "b",
"type": "string",
"options": {
"grid_columns": 4,
"grid_break": true
}
},
"c": {
"title": "c",
"type": "string",
"options": {
"grid_columns": 6
}
},
"d": {
"title": "d",
"type": "string",
"options": {
"grid_columns": 6
}
}
}
}
ace: {minLines: 5}
选项。使用enum和enum_titles:
genSnFlag: {
title: "genSnFlag/产品序列号规则",
type: "integer",
enum: [1, 2],
options: {
enum_titles: [
"1-热像(10位:2位产品线+2位系列+6位数字)",
"2-OEM(9位:9+4位订单号+4位数字)"
]
}
},
或使用enumSource, source指定一个数组:
{
"enumSource": [{
// A watched field source
"source": [
{
"value": 1,
"title": "One"
},
{
"value": 2,
"title": "Two"
}
],
"title": "{{item.title}}",
"value": "{{item.value}}"
}]
]
}
动态下拉列表,使用enumSource:
{
"type": "object",
"properties": {
"possible_colors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"text": {
"type": "string"
}
}
}
},
"primary_color": {
"type": "string",
"watch": {
"colors": "possible_colors"
},
"enumSource": [{
"source": "colors",
"value": "{{item.text}}"
}]
}
}
}
更多用法,参考jsonEditor.README.md中Enum Values
章节。
默认jsoneditor的配置会显示对象的所有选项,并在每个选项前添加checkbox。
required: true
就不会添加checkbox;defaultProperties
则指定默认只显示某些选项。更多选项需要从Properties框中选择出来才有。{
title: "虚拟字段配置",
type: "array",
format: "tabs",
items: {
title: "字段组",
type: "object",
properties: {
res: {
...
required: true
},
join: {
...
},
require: {
...
},
default: {
...
required: true
},
},
defaultProperties: ["res", "join", "default"],
}
}
通过$ref
引用:
var schema = {
"type": "object",
"properties": {
"name": {
"title": "Full Name",
"$ref": "#/definitions/name"
},
"location": {
"$ref": "http://mydomain.com/geo.json"
}
},
"definitions": {
"name": {
"type": "string",
"minLength": 5
}
}
};
({
schema: schema,
ajax: true
})
注意:引用外部文件限制只能为json文件,而且要加ajax:true
选项。
递归调用,比如菜单结构,menus是menu的数组,每个menu又可以包含menus(子菜单): 使用oneOf区分不同类型。注意oneOf只能根据数据大类型区分(object,string,array,number,null等),如果类型相同,则无法自动区分,比如它无法识别不同结构的object。
{
title: "菜单配置",
$ref: "#/definitions/menus",
format: "tabs-top",
definitions: {
menu: {
title: "菜单项",
headerTemplate: "{{self.name}}",
type: "object",
properties: {
name: {
title: "菜单名",
type: "string",
required: true
},
value: {
title: "值",
oneOf: [
{
title: "链接/代码",
type: "string",
format: "textarea",
description: '示例: <code style="margin-left:10px">http://baidu.com</code> <code style="margin-left:10px">WUI.showPage("pageUi", "物料")</code>',
options: {
input_height: "200px",
}
},
{
title: "子菜单",
$ref: "#/definitions/menus"
}
],
required: true
},
}
},
menus: {
type: "array",
format: "tabs",
items: {
$ref: "#/definitions/menu"
},
}
}
}
通过在 dependencies 中指定:
type: {
title: "类型",
type: "string",
enum: [
"s",
"i",
"n",
"subobj"
],
default: "s",
required: true,
options: {
enum_titles: [
"s-字符串",
"i-整数",
"n-小数",
"subobj-子对象"
]
}
},
// 仅当type=i时出现
linkTo: {
type: "string",
options: {
dependencies: {
type: "i"
}
}
},
// 仅当type=subobj时出现
uiMeta: {
title: "uiMeta/页面名",
type: "string",
options: {
dependencies: {
type: "subobj"
}
}
}
结构为{type="mail|wxmsg", value}
,当type不同时,对应value的结构定义也不同。 json editor并不支持这种方式。 我们换用{type, value_mail, value_wxmsg}
这种方式, 在显示对话框时(使用onInit回调)将原{type,value}
格式转为此种json editor支持的格式, 在保存时(使用onValidate回调),再做相反的转换。
var schema = {
title: "任务配置",
type: "array",
format: "tabs",
items: {
title: "任务配置",
type: "object",
headerTemplate: "{{self.name}}",
properties: {
type: {
title: "类型",
type: "string",
enum: ["mail","wxmsg","httpCall"],
default: "mail",
required: true,
options: {
enum_titles: [
"mail-邮件",
"wxmsg-微信公众号消息",
]
}
},
value_mail: {
$ref: "#/definitions/mail",
required: true,
options: {
dependencies: {
type: "mail"
}
}
},
value_wxmsg: {
$ref: "#/definitions/wxmsg",
required: true,
options: {
dependencies: {
type: "wxmsg"
}
}
}
}
},
definitions: {
mail: {
title: "邮件发送配置",
type: "object",
properties: {
...
}
},
wxmsg: {
title: "微信公众号消息配置",
type: "object",
properties: {
...
}
}
}
};
({
schema: schema,
// {type, value} => {type, value_mail/value_wxmsg}
onInit: function (data) {
if (! $.isArray(data))
return;
data.forEach(function (e) {
e['value_'+e.type] = e.value;
delete e.value;
});
},
// {type, value_mail/value_wxmsg} => {type, value}
onValidate: function (data) {
data.forEach(function (e) {
e.value = e['value_'+e.type];
for (var k in e) {
if (k.indexOf('value_') == 0) {
delete e[k];
}
}
});
}
})
使用js编辑器输入js代码选项。指定type: "javascript"
。 已内置ace编辑器,其中会检查代码语法。onNotify中对代码有没有运行错误进行检查(扩展功能,下面章节介绍)。
opt: {
title: "opt/配置代码",
type: "string",
format: "javascript",
options: {
input_height: "200px",
ace: {
minLines: 5,
},
onNotify: function (val, isManualChange) {
// 验证代码是否正确
if (isManualChange && val) {
WUI.evalOptions(val, {});
}
}
},
description: "<a class='easyui-linkbutton btnExample' href='javascript:;'>查看示例</a>"
},
使用php编辑器。指定type: "php"
。 下面mode
中的inline:true
配置,用于指定是纯php代码,不带<?php
头,不支持html混合。
onInit: {
$ref: "#/definitions/phpCode",
},
definitions: {
phpCode: {
type: "string",
format: "php",
options: {
dependencies: {
type: "default"
},
ace: {
mode: {path: "ace/mode/php", inline: true},
minLines: 5
}
},
}
}
使用patternProperties选项。示例:录入id => {kind, name}
结构的关联数组。
https://github.com/json-editor/json-editor/issues/144
注意:文档里没有提及patternProperties用法
{
"title": "pets",
"type": "object",
"patternProperties": {
"": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"cat",
"dog",
"bird",
"reptile",
"other"
]
},
"name": {
"type": "string"
}
}
}
}
}
patternProperties中如果指定“abc”, 则添加含有abc的就是应用指定格式。也可以用“^abc”
扩展的editor option,在属性变化时回调,常用于某属性变化后修改、隐藏、灰掉另一属性。还可以用于设置title(取代schema的headerTemplate属性,比它更灵活).
注意:由于JSONEditor自带的watch及on(‘change’)等机制不好用,一是难以监测动态添加的数组元素, 二是在新创建、加载初始化、调整数组元素顺序、删除等操作时也会多次调用,且不易区分这些场景,会造成误修改数据。
在实现A属性变化时修改B属性时,若B属性是可以手工修改的,则一定要加isManualChange判断条件,否则就会误修改数据,比如修改完关闭窗口后再重新打开窗口,B属性会又被改回默认值。
示例:当type值变化后(是个下拉框),自动填写name值。
type: {
title: "类型",
...
options: {
onNotify: function (val, isManualChange) {
if (isManualChange) {
var ed = this.parent.editors["name"];
ed.setValue(val);
}
}
}
},
示例:当uiType下拉框变化时,若值为空,则隐藏opt属性。同时,若值修改为“subobj”,则自动将type属性也修改为“subobj” 注意:JSONEditor自带的dependencies机制,但只能根据下拉框(enum)中的具体某个值来隐藏其它属性,不够灵活。
opt: {
type: "textarea",
options: {
dependencies: {
uiType: "subobj"
}
}
}
实现:使用onNotify回调:
...
type: "object",
properties: {
type: {
title: "类型",
type: "string",
enum: [
"s",
"i",
"subobj"
],
default: "s",
options: {
enum_titles: [
"s-字符串",
"i-整数",
"subobj-子对象"
]
}
},
uiType: {
title: "uiType/UI类型",
type: "string",
enum: [
"combo",
"upload",
"subobj"
],
default: null,
options: {
enum_titles: [
"combo:下拉列表-值映射",
"upload:图片或文件",
"subobj:子对象"
],
onNotify: function (val, isManualChange) {
// 获取同级其它editor
var edOpt = this.parent.editors["opt"];
// editor可能不存在,比如从properties对话框中没有选择它
if (edOpt) {
// 当isManualChange参数为false时,由于其它editor此时可能尚未初始化完,所以对它的操作放在setTimeout中避免出错
setTimeout(function () {
// 显示或隐藏元素,用ed.container取到其DOM元素
$(edOpt.container).toggle(!!val);
// edOpt.disable();
});
}
// 加isManualChange判断,意味着如果初始化值uiType为subobj但type是其它值,就不会去处理它
if (isManualChange && val == "subobj") {
var edType = this.parent.editors["type"];
edType.setValue("subobj");
}
}
}
},
opt: {
type: "string",
format: "textarea"
}
}
this.parent.editors[属性名]
可以取到同级其它属性的editor。 取全局editor可以用this.jsoneditor.getEditor("root.0.type")
ed.setValue(val)
设置值,通过ed.container
取对应DOM元素。示例:当name修改时,自动根据name填写type默认值。
{
type: "array",
items: {
title: "字段",
type: "object",
properties: {
name: {
title: "名称",
type: "string",
options: {
// !!! 在name属性下的options下 !!!
onNotify: function (val, isManualChange) {
if (! isManualChange)
return;
var typeVal = UiMeta.guessType(val);
var edType = this.parent.editors["type"];
edType.setValue(typeVal);
}
}
},
type: {
title: "类型",
type: "string",
}
}
}
}
由于只是填写默认值,人工可以再修改它。所以必须通过isManualChange判断后再修改,避免初始化、数组移动等操作导致人工修改的值被重置为默认。
以tab页签方式显示数组,页签标题为name和type属性拼合而成:
{
title: "字段配置",
type: "array",
format: "tabs", // tab页签式显示
items: {
title: "字段",
type: "object",
// headerTemplate: "{{self.name}}({{self.type}})",
options: {
// 与上面注释掉的headerTemplate作用相同;函数式处理更灵活,可用于无法简单属性拼接等复杂情形
onNotify: function (val, isManualChange) {
return val.name + '(' + val.type + ')';
},
},
properties: {
name: {
title: "名称",
type: "string",
},
type: {
title: "名称",
type: "string",
}
}
}
}
此例中设置标题逻辑比较简单,一般建议直接使用JSONEditor默认支持的headerTemplate。
以下是个复杂示例,它根据res数组及join属性,自动生成title,逻辑为:
如果没有join,则取res[0],若格式为“emp.name empName”,则标题用“empName”
type: "object",
options: {
onNotify: function (e, isManualChange) {
if (e.join && e.join.match(/JOIN\s*(\w+)/i)) {
return RegExp.$1;
}
if (e.res && e.res[0] && e.res[0].match(/(\w+)$/)) {
return RegExp.$1;
}
}
}
示例:为opt属性框添加“查看示例”按钮,点击则根据同级uiType属性的值,自动将示例填入opt属性框
实现:schema/uicols.js中定义如下:
title: "字段",
type: "object",
properties: {
...
uiType: {
title: "uiType/UI类型",
type: "string",
enum: [
"combo",
"subobj"
],
default: null,
},
opt: {
title: "opt/配置代码",
type: "string",
format: "textarea",
options: {
input_height: "200px",
onClick: function (ev) {
if ($(ev.target).is(".btnExample")) {
var field = this.parent.getValue();
this.setValue(examples[field.uiType]);
}
}
},
description: "<a class='easyui-linkbutton btnExample' href='javascript:;'>查看示例</a>"
}
}
ev.target
是哪个按钮来绑定按钮。jsoneditor选项增加onReady回调,可从中取到jsoneditor对象及其DOM控件,如:
({
schema: ...
onReady: function () {
// this: jsoneditor对象
// this.root_container: DOM对象
}
})
jsoneditor选项增加onInit(data)
和onValidate(data)
回调, onInit用于对数据进行预处理。 onValidate用于修改和验证数据,当返回false时,取消提交。示例见示例:每种类型具有不同的值结构.
({
schema: schema,
disable_array_delete_all_rows: true,
disable_array_delete_last_row: true,
disable_collapse: true,
disable_edit_json: true,
disable_properties: true,
onReady: function () {
// this: jsoneditor对象
// this.root_container: DOM对象
var jo = $(jsonEditor.root_container);
jo.find(".card-title.je-object__title").not(":has(':checkbox')").hide()
}
})
比如将root.alarmRuleList下的表格字段很多,为避免字段宽度过小,将它放大3倍,并显示横向滚动条。在onReady中:
var jo = $(jsonEditor.root_container);
var j1 = jo.find("[data-schemapath='root.alarmRuleList']");
j1.find(".card:first").css("overflow", "auto");
j1.find(".card:first table:first").css("width", "300%");
该字段定义示例为:
alarmRuleList: {
type: "array",
format: "table",
items: {
type: "object",
properties: { ... }
}
}