一般动作
登录
Wiki: BROPEN ▼: Document Index
»
Space: FAQ ▼: Document Index
»
Page: 表单规则开发说明
搜索
zh
页面动作
导出 ▼: 以PDF格式导出 以RTF格式导出 以HTML格式导出
更多动作 ▼: 打印预览 察看源码
欢迎进入Wiki » FAQ » 表单规则开发说明?

# 表单规则开发说明?

在2015-05-22 15:20上被李小翔修改
评论 (0) · 附件 (0) · 记录 · 信息
目录

- 规则定义
  - 页面元素定义与事件绑定 - define
  - 其他页面元素定义 - defineOnly
  - 规则定义 - ruleXxxx
  - 自定义操作 - handlers
- 规则执行
- 注意事项 & 其他

项目实施中，表单开发是二次开发最重要、最复杂、最不易维护的部分之一。

表单开发中，最常见的场景包括：

- 页面加载后，基于某个属性的值，执行业务逻辑
- 某个表单字段修改后，根据新的值，执行业务逻辑

而表单业务中，最常见的不外乎：

- 显示/隐藏某些表单部分
- 设置/清空表单字段的值
- 启用/禁用/必填/只读表单字段

常规的开发方式一般：

- 在 body 标签的 onload 事件、或者使用 js 绑定表单加载事件来执行业务，如 $j(function(){ .... }}
- 在各个表单字段的 onchange/onclick 事件、或者使用 js 绑定上述事件来执行业务，如 $j("#foo").on("change", function(){ ... }) 、$j(document).on("change", "#bar", function(){ ... })

诚然，上面的开发方式没有任何问题、也被大家所熟知，但是在实际开发过程中，很容易导致大量类似/重复的代码、以及混乱的调用关系让人抓狂、并且难于调试；

因此 BroFWK 提供了一个 JavaScript 的表单规则引擎，以提升表单业务开发的效率与可维护性、并且便于调试。

# 规则定义

先看下面的代码，基本上涵盖了表单规则中的所有元素：

$j.formRules({
// 页面元素定义、并绑定事件
define: {
foo1: {onload: true}, // == {selector: "#foo1", on: "change", onload: true},
foo2: {selector: "input[name='foo2']", on: "click"},
foo3: ".foo3" // == {selector: ".foo3", on: "change"}
},
define2: "foo4, foo5", // == ["foo4", "foo5"],
// 其他页面元素定义
defineOnly: {
bar1: ".bar1" // == {selector: ".bar1"}
},
defineOnly2: "bar2, bar3", // == ["bar2", "bar3"]
// 规则定义
ruleFoo1: {
"for": "foo1, foo2" // 或 "foo1, foo2", ["foo1", "foo2"]
"when": function(ss, vals) {
console.log(ss.target); // 使用jQuery包装的、触发事件的页面元素
return vals.foo1 == "123";
},
"when2": "123", // 或 ["123", "456"]，表示值为 123 或 456
"then": function(ss, vals) {
ss.foo1.hide();
ss.bar1.show();
},
"then2": {
"*": "hide",
"bar2, bar3": "hide, disable, abc",
".bar4": "show, !disable"
},
"else": function(ss, vals) {
ss.foo1.show();
ss.bar1.hide();
},
"else2": { ... }
"follow": "ruleFoo2" // 或 "ruleFoo2, ruleFoo3"、["ruleFoo2", "ruleFoo3"]
},
// 在 then、else 中执行的自定义操作
handlers: {
abc: function( tgt, ss, vals ) {
...
}
}
})

将上面的代码分为几个部分解释。

- 使用 $j.formRules 来定义规则，参数为一个 Map
- Map 包含 define、defineOnly、ruleXxx、handlers 等几段，除了 define、ruleXxx 外，其他可选

## 页面元素定义与事件绑定 - define

formRules 的第一段为一个或多个 define 元素，这里定义的元素会自动绑定事件（如 onchange），通过事件触发规则。

定义方式可以为 Map 或字符串、数组等，因此为避免重名，可以在 define 后加数字或其他字符区分，如上例分别采用了不同的方式定义了 define 和 define2。

- Map 结构
  - key 为定义的名称（在for、各个回调函数中使用）
  - 如果 value 也是 Map 结构，则包括：
    - selector: HTML元素的jQuery选择器，如果selector未设置，则默认为 "#key值"；
    - on: 数组或者以逗号分隔的监控事件名列表，默认为 "change"，即监控HTML元素的 onchange 事件；
    - onload：是否在页面加载完后立即针对本元素运行一次规则引擎。
  - 如果value为字符串，则等同于 Map 中的 selector，即 foo:"#foo" 等于 foo:{selector:"#foo"}。
- 为数组或者以逗号分隔的HTML元素ID
  如 "foo, bar" 或 ["foo", "bar"]，等同于 {foo:{selector:"#foo", on:"change"}, bar:{selector:"#bar", on:"change}}

这里定义的 key，在定义规则时，可以用于规则的 for/when/then/else 等处。

## 其他页面元素定义 - defineOnly

定义不绑定任何事件的表单元素，结构同 define。

## 规则定义 - ruleXxxx

一个 formRules 中可以有多个规则，每个规则的名称固定以 rule 开头，其结构为一个 Map，包括：

- for
  规则适用范围，数组或者以逗号分隔的定义名（rules.define）列表，只有这些元素触发的(通过下面的follow触发的忽略本参数)，才会执行此规则
- when
  规则适用条件：
  - 如果没有设置when条件、或者设为true值、或不为0的数字，则表示始终满足条件；如果为false或数字0，则表示始终不满足条件；
  - 如果为回调函数，则
    - 可以接收session、values参数，"session.定义名" 可以获得jQuery包装的对象，"values.定义名"可获得其值（等于session.foobar.val()），"session.target" 可获得触发规则的jQ对象
    - 函数返回true则表明满足条件；
  - 如果for中只包含一个定义，且when为数组或字符串，如果数组包含for的值或者字符串等于for的值，则表明满足条件。
- whenXx
  如果有多个适用条件，可以设置多个以 when 为前缀的适用条件，它们之间的关系为“或”。
- then
  如果满足条件，则执行 then 中的操作，类型为回调函数或Map：
  - key为以逗号分隔的定义名或jquery selector，或者用 * 表示 for 中配置的所有定义；
  - value
    - 操作函数（接收session参数和values参数）；
    - 或者数组或以逗号分隔的操作名
      - 操作可以通过 handlers 定义操作（如上面代码中的abc）
      - 或触发 key 对应的页面元素的某个事件，如 fireClick、fireChange
      - 或直接调用内置操作：
        - disable、!disable：禁用、启用
        - readonly、!readonly：设为只读、取消只读
        - required、!required：设为必填、取消必填
        - clear：清空值
        - hide、show：隐藏、显示
        - hideTD、showTD：隐藏所在单元格、显示所在单元格
        - hideTR、showTR：隐藏所在行、显示所在行
- thenXx
  当一个规则中包含多个业务、或者需要用不同的定义方式（回调函数或Map）时，可以将 then 拆成多个，如 then1、then2，便于代码维护
- else
  如果不满足条件，则执行，同 then
- elseXxx
  同 else
- follow
  如果满足条件，执行完 then 中的操作后，则继续执行其他规则，这里为规则名称数组或以逗号分隔的字符串。

## 自定义操作 - handlers

then/else执行时，虽然内置操作 + 回调函数能满足大多数需求，但是这里仍然可以定义其他操作，以便简化 then/else 中的代码，使其可读性、可维护性更强。如上例中的 abc。

# 规则执行

以项目中的实际代码为例：

<script>
$j.formRules({
define: {
allGranted: {onload: true}
},
defineOnly: {
roles: ".roles"
},
ruleHideRole: {
"for": "allGranted",
"when": function(ss, vals) {
return vals.allGranted > "0"
},
"then": {
"roles" : "hide"
},
"else" : {
"roles" : "show"
}
}
})
</script>
<tr class="prop">
<td class="name">xxxx</td>
<td class="value" colspan="3">
<g:select name="allGranted" ..../>
</td>
</tr>
<tr class="prop roles">
.....
</tr>
<tr class="prop roles">
.....
</tr>

页面上定义了一个 allGranted 的下拉列表，一个多个 class 为 roles 的 tr；定义了规则 ruleHideRole，当 allGranted 的值大于 0 时，隐藏 roles 行，否则显示 roles 行，并且在页面加载时运行一遍该规则。

通过 F12 监控控制台，修改 allGranted 的值时，可以看到下面的输出，整个规则的执行过程一目了然：

init session for allGranted, values are
Object { allGranted="1", roles=""}
test ruleHideRole.
execute ruleHideRole, when=true
do [hide] for [roles].
end session for allGranted.

下面再贴几段项目代码供学习。

$j.formRules({
define: "employeeId",
// 切换员工，重新加载，以便显示相关岗位
ruleEmployee : {
"for": "employeeId",
"then": function(ss, vals) {
if ( vals.employeeId && "create" == "${params.action}" ) {
$j(".changed").removeClass("changed");
var url = "${createLink(action:'create')}";
url += "?" + $j("form:first").serialize();
location = url;
}
}
}
})
$j.formRules({
"define": {
insAmountReport : {selector: "[name='ins.amountReport']"},
insActiveDate: {selector: "[name='ins.activeDate']"},
activeDate: {selector: "#activeDate"},
form: {selector: "form", on: "submit"},
employee: {}
},
// 生效日期变更
ruleActiveDate : {
"for": "activeDate",
"then" : function( ss, vals ) {
if ( vals.activeDate ) {
ss.insActiveDate.each(function(i, el) {
if ( !$j(el).val() ) {
el.value = vals.activeDate.substring(0, 7); // 基数的生效日期精确到月份
$j(el).trigger("change");
}
})
}
}
},
// 申报基数变更
ruleInsAmountReport : {
"for": "insAmountReport",
"when": function( ss, vals ) {
return vals.insAmountReport && isFinite(vals.insAmountReport);
},
"then": function( ss, vals ) {
// 计算核定基数等
var calError = calInsBaseAmount( ss.target.closest("tr") );
// 给其他空行赋值
ss.insAmountReport.each(function(i, el) {
if ( !el.value ) {
el.value = vals.insAmountReport;
if ( !calError ) calInsBaseAmount( $j(el).closest("tr") );
}
})
},
"else": function( ss, vals ) {
// 如果申报基数不合法，则清空核定基数等
ss.target.closest("tr").find("[name='ins.amount'], [name='ins.amountStatus']").val("");
}
},
// 基金生效日期变更
ruleInsActiveDate : {
"for": "insActiveDate",
"then": function( ss, vals ) {
// 计算核定基数等
var calError = calInsBaseAmount( ss.target.closest("tr") );
// 给其他空行赋值
ss.insActiveDate.each(function(i, el) {
if ( !$j(el).val() ) {
el.value = vals.insActiveDate;
if ( !calError ) calInsBaseAmount( $j(el).closest("tr") );
}
})
}
},
// 表单保存前，重新核定基数
ruleSubmit: {
"for": "form",
"then": function( ss ) {
ss.insAmountReport.each(function(i, el) {
calInsBaseAmount( $j(el).closest("tr") );
});
}
},
// 员工变更时，加载对应公司的薪酬结构
ruleEmployeeChange: {
"for": "employee",
"then": function( ss, vals ) {
var ps = $j("#payrollStruct"), val = ps.val();
ps.html(""); // 先清空
if ( vals.employee ) {
var opts = ps.get(0).options;
$j.post("${createLink(action:'getPayrollStructures')}", {employeeId: vals.employee}, function(data) {
$j(data).each(function(i, m) {
opts[i] = new Option(m.name, m.id);
})
});
}
}
}
})

# 注意事项 & 其他

阅读完上面的代码和长长的说明后，可能会觉得这样上面的代码写起来貌似更复杂、代码量更多了，其实不然：

- 把第一段代码格式化后、保存到一个地方，需要的时候拿出来改一下就可以用，不需要死记整个结构的写法
- 将复杂的业务拆分成元方法，再通过规则调用，代码会显得特别干净和清晰，易于维护
- 从代码量的角度来说，行数可能会增加，但代码量总体上来说肯定是减少了的
- 在个人的开发过程中，编写表单规则时的唯一常见问题就是 Map 结构中，每个 key/value 定义后需要加个逗号，否则 js 编译报错，比如： ruleXx: {...}, rule:Yy{...}

标签： BroFramework
在2015-05-22 14:01上被李小翔创建

- 评论 （0）
- 附件 （0）
- 记录
- 信息

# 快速链接

- 首页
- 新闻动态
- FAQ

Copyright © 2013 北京博瑞开源软件有限公司
京ICP备12048974号