## 行为
HTML事件系统使得前端不仅仅是简单的视图呈现, 而且能够使得应用与用户进行交互。应用的所有业务逻辑基本由事件触发,UOkay对HTML事件系统封装了行为。我们给行为下了一个定义:
行为是对用户操作视图触发事件所做出的响应。
### 用法
~~~
<div id = "app">
<input type="text" action-change="form.check_number" model = "number"/>
<if condition = "err == true">
数字格式错误
</if>
</div>
~~~
~~~
class FormAction extends Action {
check_number(okay, event, pipe) {
let value = okay.data.number
if(/^[0-9]*\.?[0-9]*$/.test(value)) {
okay.data.err = false
}else {
okay.data.err = true
}
}
}
var app = new Okay({
ele:'app',
data: {
err:false,
number:''
},
action_classes: {
form: FormAction
}
})
~~~
开发者可以定义Action类继承自Action, 并将其指定给Okay配置action_classes。上述配置完成后,并可以给视图事件指定行为:定义action属性,action属性的语法如下:
~~~
action-事件名 = "行为A=>行为A返回1:下一行为C,行为A返回2:下一行为B|行为B|行为C|..."
~~~
行为之间由“管道”符号分割。
### 行为函数
行为函数是Action的函数,它接受三个参数,分别为:Okay,Event,Map。
* okay是触发该行为函数的Okay对象,可以通过该参数进行对okay的控制,从而控制视图;
* event是相应的事件对象
* pipe是一个Map对象,该参数提供了行为函数链中数据的传递,我们可以通过该参数将行为函数的处理结果传递到下一个行为函数。
### 行为路由
UOkay推荐行为函数尽量符合单一原则,即一个行为函数尽量处理单一业务功能,而不是将过多的业务逻辑代码堆砌在一个行为函数中造成后期维护调试的困难。
对于单一功能如何组合形成完整的业务逻辑,我们提供了一套行为路由规则,例如:
~~~
<div id = "app">
<input type="text" action-change="form.check_number|form.check_tel=>email:form.check_email|form.check_qq|form.check_email" model = "number"/>
<div>
{{msg}}
</div>
</div>
~~~
~~~
class FormAction extends Action {
check_number(okay, event, pipe) {
let value = okay.data.number
if(/^[0-9]*\.?[0-9]*$/.test(value)) {
okay.data.msg = '输入的是数字'
}else {
pipe.set('value',value)
return true
}
}
check_tel(okay, event, pipe) {
let value = okay.data.number
if(/^1[3|4|5|7|8][0-9]\d{4,8}$/.test(value)) {
okay.data.msg = '输入的是手机号码'
}else {
return 'email'
}
}
check_qq(okay, event,pipe) {
let value = pipe.get('value')
if (/^[1-9][0-9]*/.test(value)) {
okay.data.msg = '输入的是QQ'
}
}
check_email(okay, event, pipe) {
let value = pipe.get('value')
if(/^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/.test(value)){
okay.data.msg = '输入的是邮箱'
}else {
okay.data.msg = '输入数据错误'
}
}
}
var app = new Okay({
ele:'app',
data: {
msg:'',
number:''
},
action_classes: {
form: FormAction
}
})
~~~
~~~
action-change="form.check_number|form.check_tel=>email:form.check_email|form.check_qq|form.check_email"
~~~
上述行为过程为
* 执行form.check_number;
* 若form.check_number无返回值或返回Action.EOA,则行为结束;
* 若form.check_number有返回,如例子return true,则按顺序执行form.check_tel;
* form.check_tel有三种执行结果,一种返回email,则执行form.check_email,若返回其他则执行form.check_qq,若无返回则结束行为;
### 参数属性
对于行为函数,我们推荐保持行为函数的独立性,即与Okay对象的隔离。在上例中,行为函数`check_number`中value值是通过`okay.data.number`获得,那么对于其他Okay对象(组件)复用该行为函数时,必须有相同的数据结构即`okay.data.number`,这极大的限制了行为函数的复用,我们推荐采用参数属性进行数据的传递,例如:
~~~
<div id = "app">
<input type="text" action-change="form.check_number" model = "number" action-change-props = "value:{{number}}"/>
<if condition = "err == true">
数字格式错误
</if>
</div>
~~~
~~~
class FormAction extends Action {
check_number(okay, event, pipe) {
let value = pipe.get('value')
if(/^[0-9]*\.?[0-9]*$/.test(value)) {
okay.data.err = false
}else {
okay.data.err = true
}
}
}
var app = new Okay({
ele:'app',
data: {
err:false,
number:''
},
action_classes: {
form: FormAction
}
})
~~~
我们只需设置action-事件名-props参数,并通过属性模板进行数据的绑定,在行为函数中通过pipe.get("数据名")即可获得该数据,实现了Okay与Action的分离。
### AOP(面向切面)
如上例所示,我们不能很好的监听行为函数链中各个行为的状态,如何将状态反馈给Okay对象(组件)也是限制Action独立性的一个问题,如上例中,我们必须使用`okay.data.err`对Okay对象的直接引用来反馈结果,硬耦合必定会使维护成本加大。
为了解决这种硬耦合,我们引入了AOP,增加了两个属性:`aop-事件名-before`,`aop-事件名-after`。`aop-事件名-before`是行为函数的前置函数配置,`aop-事件名-after`是行为函数的后置函数配置。配置语法如下:
~~~
aop-事件名-before="行为函数1:前置函数名1,行为函数2:前置函数名2,..."
aop-事件名-after="行为函数1:后置函数名1,行为函数2:后置函数名2,..."
~~~
例如:
`aop-change-before="form.check_number:set_param,form.check_tel:tel_result,..."`
在执行form.check_number行为函数前,我们会执行set_param该前置函数,接受pipe参数。
有两个特殊的行为函数名'`_start_`'(前置),'`_end_`'(后置),前者是该行为链发生前一定执行的函数,后者是行为链发生后执行的函数。有了AOP,我们可以修改上例如下:
~~~
<div id = "app">
<input type="text" action-change-props = "value:{{number}}" aop-change-after = "_end_:info_result" action-change="form.check_number|form.check_tel=>email:form.check_email|form.check_qq|form.check_email" model = "number"/>
<div>
{{msg}}
</div>
</div>
~~~
~~~
class FormAction extends Action {
check_number(okay, event, pipe) {
let value = pipe.get('value')
if(/^[0-9]*\.?[0-9]*$/.test(value)) {
pipe.set('info','输入的是数字')
}else {
pipe.set('value',value)
return true
}
}
check_tel(okay, event, pipe) {
let value = okay.data.number
if(/^1[3|4|5|7|8][0-9]\d{4,8}$/.test(value)) {
pipe.set('info','输入的是手机号码')
}else {
return 'email'
}
}
check_qq(okay, event,pipe) {
let value = pipe.get('value')
if (/^[1-9][0-9]*/.test(value)) {
pipe.set('info','输入的是QQ')
}
}
check_email(okay, event, pipe) {
let value = pipe.get('value')
if(/^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/.test(value)){
pipe.set('info','输入的是邮箱')
}else {
pipe.set('info','输入数据错误')
}
}
}
var app = new Okay({
ele:'app',
data: {
msg:'',
number:''
},
action_classes: {
form: FormAction
},
aop: {
info_result: function (pipe) {
this.data.msg = pipe.get('info')
}
}
})
~~~
在上例中,Action脱离的对Okay对象的硬引用,对于okay中的数据对象的操作交给了Okay的处理,从而保证业务与数据的分离。
在Okay中我们只需要配置aop属性即可。
### 状态管理
每一个行为对象在整个应用中是单例模式,也就是所有拥有该行为的Okay(组件)都将共享该行为的数据。应用状态往往也是随着行为的发生而发生改变,所以UOkay将应用状态的管理交给了行为Action处理,我们只需重写Action的__initial__函数即可。例如:
~~~
<div id = "app">
<input type="text" aop-change-after = "_end_:info_result" action-change="form.check_number" model = "form.number"/>
<div>
{{msg}}
</div>
</div>
~~~
~~~
class FormAction extends Action {
__initial__() {
this.number = ''
}
check_number(okay, event, pipe) {
let value = this.number
if(/^[0-9]*\.?[0-9]*$/.test(value)) {
pipe.set('info','输入的是数字')
}else {
pipe.set('info','输入结果错误')
}
}
}
var app = new Okay({
ele:'app',
data: {
msg:''
},
action_classes: {
form: FormAction
},
aop: {
info_result: function (pipe) {
this.data.msg = pipe.get('info')
}
}
})
~~~
由上例可看出,input绑定的数据为行为form中的数据form.number,行为函数check_number直接通过this.number对数据进行访问,无需与Okay进行交互。
任何拥有FormAction行为的Okay(组件)将共享FormAction对象的数据。
### 组件的反向控制
行为类能对存活的组件进行反向控制,属性okays存储者所有拥有该行为的组件,例如:
`this.okays[组件实例名A]`存储着所有组件实例名为A的所有组件。
若该组件实例名在路由栈中仅由一个存活,则该引用为该组件;若在路由栈中存在多个该组件实例名的组件,则该值为一个存储所有组件的Set对象。
