# 前端开发文档
M18平台页面开发框架基于JSF。 JSF的一个基本概念是组件。页面通过一个组件树来渲染。每个UI组件通过EL表达式获取值,并将其显示在页面上;并在数据提交时,将页面上用户修改后的值,同步到服务器端;也可将用户的界面操作,转化成事件,提交到服务器,通过EL表达式响应。
# JSF页面与后台交互框架
# 界面交互概念
# JSF交互概念
JSF与后台的交互,不论是值的绑定,还是事件的响应,都是通过EL表达式来实现的。页面上的JSF组件通过EL表达式,与后台的Java Bean对象绑定,进而完成值的获取、提交,组件上的事件响应。JSF页面,与后台Java Bean的关系如下图。
# M18交互
M18中,保留JSF所有交互方式,并支持另一种交互方式。交互关系图如下:
在M18中,每一个View(JSF页面),都有一个视图控制器(ViewController),视图控制器负责事件的分发。每一个View,可以有多个事件监听者(ViewListener),监听视图控制器分发的事件,根据做出响应。 事件监听者对象,可以是一个Java Object,也可以是一个Java Bean。只需实现了ViewListener接口。
注意:
- View与后台数据绑定,只能通过JavaBean和Controller来处理。 与JavaBean通过EL表达式来绑定。而通过Controller,可以把绑定一些临时数据,具体见Controller对临时数据的支持
- ViewListener只监听事件,并做出对应的响应。
- 一般而言,一个View,推荐存在一个Java Bean,多个ViewListener。
# ViewListener
public interface ViewListener {
public default String getKey() {
return this.getClass().getName();
}
public void controllerInitialized(ViewController controller);
// for input
public boolean validateValue(ValidateEvent ve);
public void valueChange(ValueChangeEvent vce);
// for action
public void actionPerformed(ViewActionEvent vae);
// for dialog
public void dialogCallback(ViewActionEvent vae);
// for lookup
public default void lookupParameter(LookupDecorateEvent event) {
};
public default void lookupData(LookupDecorateEvent event) {
};
}
# 接口说明
接口 | 必要 | 说明 |
---|---|---|
getKey | 否 | 作为Listener在当前View的一个标识,可用于被其他ViewListener获取。见ViewListener互相访问方式 |
controllerInitialized | 是 | 当ViewController创建后,初始化时调用。可用于初始化数据。 |
validateValue | 是 | 当前端组件提交之后,对值进行校验时调用。返回false 意味着提交值没有通过校验,不会应用到后台Model中。 |
valueChange | 是 | 当组件提交值,并通过校验,应用到后台Model中后,调用。用于根据提交值,修改其他后台Model数据。 |
actionPerformed | 是 | 用于响应前端组件提交的事件。比如commandButton 的点击事件,inputText 的focus 、blur 、change 事件。 |
dialogCallback | 是 | 用于处理当前View弹出的对话框的回调事件。详细见对话框关闭和回调方法 |
lookupParameter | 否 | 在lookup 行为发生后,进行数据读取前调用。允许监听者调整lookup 的属性,进而影响数据读取。 |
lookupData | 否 | 在lookup 行为发生,读取数据后,UI显示前调用。允许监听者调整最终的数据。 |
示例:
//Examples
public boolean validateValue(ValidateEvent ve) {
String id = ve.getComponent().getId();
if("age".equals(id)) {
if((Integer)ve.getNewValue() < 0) {
return false;
}
}
}
public void valueChange(ValueChangeEvent vce) {
String id = vce.getComponent().getId();
if("code".equals(id)) {
System.out.println("Code is changed");
}
}
public void actionPerformed(ViewActionEvent vae) {
String actionCommand = vae.getActionCommand();
// String id = vae.getComponent().getId();
if("ok".equals(actionCommand)) {
System.out.println("Ok button is clicked");
}
}
public void lookupParameter(LookupDecorateEvent event) {
StParameter param = (StParameter) event.getSource();
//String componentId = event.getComponentId(); // If you want to know the UI component
param.setStSearch("user");
}
public void lookupData(LookupDecorateEvent event) {
SqlTable data = (SqlTable) event.getSource();
data.setString(1, "desc", "modified by listener");
}
注意:
- 所有的action, 包括
commandButton
,也包括前端通过ajax
标签添加的事件,都会通过actionPerformed
来响应。用户通过ViewActionEvent
对象来获取组件信息或者ajax信息。ViewActionEvent.getActionCommand()
返回一个字符串,作为事件的标识。开发者可以在对应组件上,通过属性actionCommand
来设定。<caw:commandButton actionCommand="showDialog"/> <caw:inputText> <caw:ajax event="change" actionCommand="showDialog"/> </caw:inputText>
# 为View添加ViewListener
开发者为视图(view)增加ViewListener有如下几种方式
简单的ViewListener对象(不是Java Bean)。在
navmenu.xml
中,为指定menu配置listener。<menu code="employee" ...> <listener>com.multiable.bean.view.ModuleViewListenerTest</listener> <listener>com.multiable.bean.view.ModuleViewListenerTest2</listener> </menu>
对于Java Bean对象的ViewListener。可以直接在JSF页面声明。
<html ...>
<caw:view content="text/html">
<h:body>
<h:form id="formId">
<caw:beanDeclare name="employee"></caw:beanDeclare>
</h:form>
</h:body>
<caw:view>
</html>
在JSF页面,通过JAVA类名指定一个ViewListener对象
<caw:viewListener class="com.multiable.bean.view.ModuleViewListenerTest"/>
在Bean中,通过代码生成ViewLIstener,并在JSF页面绑定。
<caw:viewListener value="#{employeeBean.listener}"/>
// EmployeeBean.java public ViewListener getListener() { return new ModuleViewListenerTest(); }
# 互相访问方式
在ViewListener内部,如果期望访问当前View中的另一个ViewListener, 你必须知道另一个ViewListener的key,对应ViewListener中的getKey()
接口。
key的默认值为ViewListener的className。在ViewListener实例中通过加载getKey()
来修改。
在ViewListener中,可以通过如下代码获取其他ViewListener实例(不包含JavaBean形式的ViewListener,因为你可以直接通过注入方式,调用其他Java Bean):
ViewListener[] listeners = controller.getViewListener(listenerKey);
# ViewController
# 获取Controller
在ViewListener中,如果想获取当前视图(View)的控制器对象,通过如下代码
ViewController controller = CawBeanUtil.getViewController();
如果Java Bean形式的ViewListener继承于
viewBean
、ModuleBean
, 和其他继承于ViewListenerBase
的ViewListener, 在内部可以直接使用变量controller
来访问当前View的控制器。因为这几个基类中,在controllerInitialized
中,保存了当前View的控制器到变量controller
中。
# Controller对临时数据的支持
JSF页面,通过EL表达式,与Java Bean中的变量(具有set/get接口)进行绑定。 如果需要通过EL表达式,与View中一些非持久化的变量进行绑定,我们可以通过Controller来设置变量,而不需要在Java Bean中新增变量以及对应的set/get接口。 这个动作在所有ViewListener中都可以完成。
- 设置变量值
controller.setVariable(String key, Serializable value)
controller.setVariable("age", 18); controller.setVariable("name", "John"); controller.setVariable("createTime", new Date());
- 获取变量值
controller.getVariable(String key)
int age = (Integer) controller.getVariable("age"); String name = (String) controller.getVariable("name"); Date createTime = (Date) controller.getVariable("createTime");
# ViewController/ViewListener层级
注意:
- 每一个视图(View)都有一个视图控制器,但是视图控制器的实例对象可能是上述的某一种视图控制器对象。
- 任意一个ViewListener对象都可以添加到任意一个视图。但是不同的ViewListener接口,存在其特殊的接口方法, 而这些接口方法仅仅在特定的视图控制器中才有效。
- 视图(View)的分类,以及其对应的视图控制器,请参阅M18 UI概念
# M18 UI概念
M18平台运行在一个浏览器窗口中,M18平台中的文档、模块以标签页的形式显示在M18浏览器窗口中。
- M18浏览器窗口视图,称为“App视图”
- 在“App视图”中,以标签页显示的视图称为“Frame视图”
- 在“App视图”中,弹出的窗口,称之为“对话框(Dialog)视图”
- 在“App视图”中,用来显示资料信息的悬浮窗口,称之为“资料名片(Namecard)视图”
- 所有视图,都有独立的视图文件(JSF文件,xhtml格式)
App视图
Frame视图
对话框(Dialog)视图
资料名片(Namecard)视图
模块(Module)视图
等同于Frame视图。模块(Module)只是一类特殊的Frame视图,是在Frame视图上绑定了后台定义的module数据。
# 自定义布局介绍
Frame视图支持用户级别的自定义布局。
M18使用fluidPanel
组件来实现“自定义布局”功能。
# 布局组件fluidPanel
介绍
fluidPanel
组件是M18提供的一种布局组件。它基于bootstrap的12栅格系统,采用流式布局+响应式布局的方式,根据子组件的布局配置,为其子组件分配显示空间。
fluidPanel
主要属性介绍
属性名 | 类型 | 说明 |
---|---|---|
column | int | 为当前布局指定垂直分栏数量。由于fluidPanel 基于12栅格系统,因此分栏数量必须是12的约数:1、2、3、4、6、12,推荐使用1、2、3、4。 |
更多属性介绍见组件使用文档
fluidPanel
子组件配置布局属性
fluidPanel
子组件通过标签constraints
(命名空间http://www.cloud-at-work.com
)进行布局属性配置。如下:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:caw="http://www.cloud-at-work.com"...>
...
<caw:fluidPanel id="layoutPanel" column="3">
<caw:inputText id="text1" ...></caw:inputText>
<caw:inputText id="text2" ...>
<caw:constraints colSpan="2"></caw:constraints>
</caw:inputText>
<caw:imgUpload id="img1" ...>
<caw:constraints rowSpan="4"></caw:constraints>
</caw:imgUpload>
</caw:fluidPanel>
...
</html>
constraints
主要属性介绍
属性名 | 类型 | 说明 |
---|---|---|
rowSpan | int | 组件在布局垂直方向跨越的行数 |
colSpan | int | 组件在布局水平方向跨越的列数(栏数) |
newLine | boolean | 组件将在新的一行前面 |
rowFill | boolean | 组件将占据当前行剩余的空间 |
rowHold | boolean | 组件将占据一整行。如果当前行已经有其他组件存在,则该组件将占据下一行空间。 |
styleClass | String | 给组件所占布局区域(DIV )设置class |
style | String | 给组件所占布局区域(DIV )设置样式style |
# 布局规范
开发者如果期望编写的Frame视图支持自定义布局功能,必须遵行以下规则:
- 使用
fluidPanel
组件来进行布局 - 只有
fluidPanel
的直接子组件参与“自定义布局” fluidPanel
组件必须指定id
属性,并且id
在视图内是唯一的- 参与“自定义布局”的
fluidPanel
子组件必须指定id
属性,并且id
在视图内是唯一的
给开发者的建议:
不要嵌套使用fluidPanel ,如下
<caw:fluidPanel id="layout" column="2"> <caw:fluidPanel id="nest" column="2"> ... </caw:fluidPanel> ... </caw:fluidPanel>
位置相对固定的组件,不要直接作为
fluidPanel
的子组件。如果你期望一些组件作为一个整体放在
fluidPanel
下,可以使用其他布局相关组件,对这些组件进行封装,再放入fluidPanel
下。这样的布局组件有fieldGroup
,container
。
# 组件编写规则与要求
ID必须填写,并且唯一
- 我们的页面是允许第三方开发者进行扩展,第三方开发者是通过组件
ID
来进行定位和扩展的。 - 我们的页面是允许用户进行自定义页面布局,自定义页面时,组件的区分依据就是组件
ID
- 在代码中,进行组件的获取或者指定更新,我们是需要知道组件的
clientId
(根据ID
自动生成),而这个不固定的clientId
会严重加大维护成本,因此在M18中,我们提供了进行“组建获取和指定”的替代方案:根据固定不变的组件ID
来进行组件的获取和指定。
- 我们的页面是允许第三方开发者进行扩展,第三方开发者是通过组件
页面布局使用
fluidPanel
,页面可以分为多个区域,每个区域使用fluidPanel
布局。详细见布局组件介绍在
module
中,页面组件通过指定tableName
,columnName
映射后台数据。并能够自定生成组件id
。在一般情况下,我们编写组件需要做如下设置
id
设定和value
绑定:<caw:inputText id="text1" value="#{xxxBean.textValue}"></caw:inputText>
在module页面中,可以通过如下方法设置:
<caw:inputText tableName="xxTable" columnName="yyColumn"></caw:inputText>
通过
tableName
,columnName
设置的组件,会自动绑定到后台的entity
上去。并且生成组件id
,其规则是id=tableName_columnName
(上述例子中,组件id
是xxTable_yyColumn
)
# 页面资源引入
在所有视图文件中,只需要引入当前视图所需要的特定css
/js
资源。系统通用的资源,会自动加载。
自动加载的系统资源有:jquery.js
、tether.js
、bootstrap.js
、cawView.js
、bootstrap.css
、cawStyle.css
、cawTheme.css
等。
在特定的视图里面,也有一些特定的自动加载的资源。
# 模块(Frame)页面开发
# Frame与Module
Frame是在
navmenu.xml
中定义,在App视图中,以标签页显示的视图。在App视图中,通过点击Menu中对应的菜单项,可以进入对应的Frame视图。
Module是指在
navmenu.xml
中定义时,指定了module
属性的Frame。navmenu.xml
中配置的module
需要在module.xml
中定义,module.xml
主要用来给module
指定数据绑定,以及配置数据操作。
Frame视图的控制器是FrameController
, 事件监听接口是ViewListener
。
Module视图的控制器是ModuleController
, 事件监听接口是ModuleViewListener
# Frame页面模板
我们为module提供了一个统一的页面模板来简化页面的编写。
这个模板包含了toolbar
,searchView
,additionView
三个辅助显示区,和一个主显示区contentView
。
searchView
主要用来浏览文档记录提供默认实现,支持开发者定制
contentView
用来显示文档数据完全由开发者实现
toolbar
放置文档操作命令提供默认实现,支持开发者定制
additionView
作为contentView
的补充,显示文档数据。提供默认实现,支持开发者定制
如何使用Module模板
<!--
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:caw="http://www.cloud-at-work.com"
-->
<ui:decorate template="/view/template/viewTemplate.xhtml">
<ui:param name="adtPage" value="false"></ui:param> <!--设置参数-->
<ui:define name="toolArea">
<!--定制toolbar区域-->
</ui:define>
<ui:define name="toolbar">
<!--增加toolbar元素, 仅当没有定制toolArea时有效 -->
<caw:actionItem label="customed" action="customAction" actionListener="#{xxxBean.doAction}"></caw:actionItem>
</ui:define>
<ui:define name="searchView">
<!--定制searchView-->
</ui:define>
<ui:define name="additionPanel">
<!--定制additionView-->
</ui:define>
</ui:decorate>
模板提供的配置参数
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
hideSearchPage | boolean | false | 是否隐藏searchView |
hideRecordInfo | boolean | false | 是否隐藏”单据信息“ |
hideRightToolbar | boolean | false | 是否隐藏toolbar 右边的单据操作工具栏 |
hideToolbar | boolean | false | 是否隐藏toolbar |
hideBeSelect | boolean | false | 是否隐藏“商业中心”选择框 |
adtPage | boolean | false | 是否隐藏additionView |
searchLayoutConfigurable | boolean | true | searchView 是否可以更改显示形式 |
# Frame事件
Frame提供了多种事件供开发者监听。
# JavaScript事件
事件 | 说明 |
---|---|
Frame.Event.SIZECHANGE | 当Frame窗口大小变化时触发 |
Frame.Event.MODSTATUS | 当Frame视图中,组件值提交,导致后台数据更新时,modify 状态变化时提交 |
Frame.Event.INIT | 在Frame视图初始化时触发 |
Frame.Event.ACTIVED | 在Frame视图称为当前活跃的标签页时触发 |
Frame.Event.DEACTIVED | 当Frame视图由活跃标签页变成非活跃标签页时触发 |
Frame.Event.CLOSING | 在Frame视图关闭时触发 |
以上事件都可以使用在js文件中:
$(document).on(Frame.Event.ACTIVED, function(event) {});
$(document).on(Module.Event.ACTIVED, function(event) {});
# Java事件
通过ajax
绑定后台代码与事件:
之间支持:opened
,closed
,active
,deactived
,dialogReturn
<caw:frame>
<caw:ajax event="opened" listener="#{xxxBean.doAction}" process="@this"></caw:ajax>
</caw:frame>
# Module事件
# JavaScript事件
Module具备Frame所有的事件,同时在以下动作触发时,提供BEFORE
和AFTER
两种事件:Module.Event.BEFORE_ + 事件编码
和Module.Event.AFTER_ + 事件编码
动作 | 事件编码 | 说明 |
---|---|---|
刷新 | REFRESH | 单据刷新 |
创建 | CREATE | 创建单据 |
保存 | SAVE | 保存单据 |
另存为 | SAVEAS | 单据另存为 |
重命名 | RENAME | 单据重命名 |
保存为草稿 | SAVEASDRAFT | 单据保存为草稿 |
保存为模板 | SAVEASTEMPLATE | 单据保存为模板 |
删除 | DELETE | 单据删除 |
读取 | READ | 读取单据 |
打印 | PRINT | 打印单据 |
打印EXCEL | PRINTASEXCEL | 以EXCEL形式打印单据 |
打印HTML | PRINTASHTML | 以HTML形式打印单据 |
打印RTF | PRINTASRTF | 以RTF形式打印单据 |
切换企业 | BECHANGE | 切换企业 |
$(document).on(Module.Event.AFTER_SAVE, function(event) {});
# Java事件
Module提供了ModuleViewListener
,在ViewListener
的基础上提供更多的事件接口。
# 对话框(Dialog)开发
# 对话框简介
Dialog是M18的一种弹出窗口,它会覆盖所在容器的下层视图,并锁住下层视图(下层视图不可操作)
Dialog容器
Dialog所在视图称之为Dialog容器。
M18可以呈现Dialog的视图有两种:App视图, Frame视图
注意:
- Dialog会锁住Dailog容器,在容器内,只有当前Dialog视图处于活跃状态,直到Dialog关闭
- Dialog在Frame视图时,当前Frame视图被锁住,但是其他标签页的Frame视图可以正常操作。
- Dialog在App视图时,整个M18系统会被锁住。
Dialog类型
Dialog根据显示内容的格式分为三类:
- 文本
HTML
格式文本- 独立的JSF文件(JSF视图)
注意:
- Dialog不会阻塞进程。即使后台调用
WebUtil.showDialog
,Dialog也不会在代码执行的那一刻显示出来,而是当后台请求完成后,返回到前端后,显示Dialog的代码才能执行。- 监听Dialog关闭事件,执行回调动作,必须指定callback。 详细见对话框关闭和回调方法
- 与后台有绑定动作的Dialog,在后台都有一个CawDialog对象关联。通过CawDialog对象可以对Dialog做更多的设置。详细见CawDialog说明
# 弹出对话框
# 页面JavaScript弹出窗口
//myView是一个JsView实例(定义在CawView.js中),存在于每个JSF视图中。
//显示文本信息的Dialog
myView.showDialog({
message: 'This is a message dialog'
});
//显示HTML文本的Dialog
myView.showDialog({
html: '<p>This is a html message</p>'
});
//显示独立JSF视图
myView.showDialog({
url: '/jsf/view/dialog/dlgView.faces',
target: 'frame', //可以不设置,系统会自动判断当前视图
});
可用的Dialog配置项见Dialog配置项
# 后台Java弹出窗口
//显示文本信息
WebUtil.showMessage('Text message', 'Dialog Title');
//显示HTML文本
WebUtil.showHtmlMessage('<p>Html text message</p>', 'Dialog Title');
//显示JSF视图
WebUtil.showDialog('/jsf/view/dialog/dlgView.faces');
//使用CawDialog
CawDialog dialog = new CawDialog("/view/dialog/dlgView");
dialog.setDialogName("dlgView");
dialog.addRequestParam("param1", "value1");
dialog.addRequestParam("param1", "value1");
WebUtil.showDialog(dialog);
CawDialog对象介绍见CawDialog说明
# CawDialog对象和Dialog配置项
# CawDialog说明
CawDialog
是后台Java端用来描述对话框的一个对象。其重要方法如下:
方法 | 说明 |
---|---|
setDialogName(String name) | 指定Dialog的名称。名称可用于在Callback中区分不同Dialog`` |
setTarget(DialogTarget target) | 指定Dialog容器:DialogTarget.frame 和DialogTarget.app |
setType(DialogType type) | 指定Dialog类型:DialogType.message ,DialogType.html 和DialogType.custom |
setContent(String content) | 当类型是DialogTarget.message 和DialogTarget.html , 变量content 是需要显示的message内容;当类型是DialogTarget.custom 是,变量content 是指定的url 信息。 |
setWithResponse(boolean value) | 设定Dialog是否在关闭后执行回调动作 |
addRequestParam(String key, Object value) | 仅当类型是DialogType.custom时有效,用于设置,传入对应url 中的参数。这类参数通过request params传入。 |
addParam(String key, Object value) | 仅当类型是DialogType.custom时有效,用于设置,传入对应Dialog视图的数据,这部分数据是保存在session中。 |
setOption(String key, Object value) | 用于设置Dialog的可选配置项。可用配置项见Dialog配置项中第8~17项 |
# Dialog配置项
No | 配置项 | 类型 | 说明 |
---|---|---|---|
1 | dialogName | String | 指定Dialog的名称,名称用于在callback中区分不同的dialog |
2 | dialogId | String | dialog的唯一标识,在默认情况下(开发者没有指定),系统会自动生成一个唯一标识。 |
3 | dialogParent | String | 指定Dialog的父级Dialog(当前Dialog弹出动作所在的Dialog视图)的dialogId 。没有指定时,系统会自动检测。 |
4 | dialogHandler | String | 仅用于JSF组件开发,用于指定响应callback的组件。 |
5 | url | String | 用于指定Dialog内容的JSF视图文件 |
6 | message | String | 用于指定Dialog显示的文本内容 |
7 | html | String | 用于指定Dialog显示的Html内容 |
8 | title | String | 用于指定Dialog标题栏显示的标题 |
9 | ftype | String | 当类型为DialogType.message/DialogType.html时,用于指定Dialog下方显示的动作按钮。 可选项为confirm , askif 。 |
10 | width | int | Dialog的宽度。(以px为单位) |
11 | height | int | Dialog的高度。(以px为单位) |
12 | maxView | boolean | 是否最大化显示Dialog |
13 | resizable | boolean | Dialog是否支持调整大小 |
14 | draggable | boolean | Dialog是否支持拖拽移动位置 |
15 | maximize | boolean | Dialog是否支持最大化 |
16 | onClose | JS Function | 设置在Dialog关闭时执行的JavaScript函数 |
17 | onCallback | JS Function | 设置在Dialog关闭后,执行回调动作前,执行的JavaScript函数。 函数允许返回值。如果返回false ,那么回调动作会取消。如果返回Javascript Object, 可以设置回调动作中,传入后台的参数。 |
设置方法
设置Dialog配置项的方法有如下三种:
通过JavaScript方法显示Dialog时,通过
myView.showDialog(dialogOption)
设置参数。通过Java方法显示Dialog时,通过自定义
CawDialog
来进行配置项设置在Dialog的独立视图文件中,通过JSF标签设置,如下
<caw:dialogOption width="500" height="600" title="myTitle" draggable="true" resizable="false" maximize="false" maxView="false"></caw:dialogOption>
# 对话框关闭和回调方法
# Dialog的关闭
Dialog在关闭时,带有两个信息:
- 关闭状态
DialogStatus
, 标记Dialog关闭状态,目前有如下状态:OK
,CANCEL
,YES
,NO
- 关闭时返回的数据
returnObject
, 是Dialog传回给callback的数据
关闭Dialog的方法
JavaScript
方法:
myView.closeDialog(dialogId, status, returnObject);
Java方法:
仅仅在当前DialogView视图中,才能关闭当前Dialog。
//controller是DialogController对象
controller.closeDialog(DialogStatus status, Object returnObject);
# 回调方法
Dialog回调方法,指的是:当Dialog关闭后,系统根据Dialog关闭状态和传回的数据,执行的特定的方法。
添加回调方法有四种方式:
在Dialog的父级视图(执行弹出Dialog的视图)中,通过
dialogHandler
指定<caw:dialogHandler actionListener="#{dlgParentBean.dialogClosed}"/>
//dlgParentBean public void dialogClosed(FacesEvent event) { String dialogId = (String)event.getComponent().getAttributes().get(WebConstants.Dialog.CONVERSATION_PARAM); if (dialogId != null) { CawDialog dialog = FacesUtil.getContext().getAttributes().get(dialogId); if("dialog1".equals(dialog.getDialogName())) { //do callback for dialog1 } } }
在Dialog的父级视图中,通过
ajax
标签为特定dialog指定<caw:dialogHandler> <caw:ajax event="dialogReturn" actionCommand="dialog1" listener="#{dlgParentBean.dialog1Callback}"></caw:ajax> </caw:dialogHandler>
//dlgParentBean public void dialog1Callback() { //do callback for dialog1 }
在弹出Dialog的Java代码上指定
WebUtil.showDialog("/jsf/view/dialog/dialog1.faces", "dialog1", (dialog) -> {//do callback});
CawDialog dlg = new CawDialog("/view/dialog/ebi/ebiLinkSettingModuleRule.xhtml"); dlg.setCallback(new DialogCallback() { private static final long serialVersionUID = 1L; @Override public void processAction(CawDialog dialog) { if (DialogStatus.OK.equals(dialog.getCloseStatus())) { //do callback } } }); WebUtil.showDialog(dlg);
通过ViewListener响应
不需要任何设置代码,只需要在ViewListener的接口方法中直接处理Dialog的callback就可以了。
//任意一个Dialog父级视图的ViewListener public void dialogCallback(ViewActionEvent vae) { CawDailog dialog = vae.getSource(); if("dialog1".equals(dialog.getDialogName())) { //do callback for "dialog1" } }
这是最为简单,也是我们**推荐**的做法。
# 消息(Message)开发
# 消息简介
在后台数据处理过程中,我们需要将一些结果反馈到前台显示。这里我们使用的就是JSF消息机制。
# 消息说明
JSF在后台,在一个请求周期内,可以任意添加消息。但是JSF消息要呈现出来,必须有前端组件来负责JSF消息的渲染。所以开发者想要显示消息,就必须在JSF页面上指定渲染消息的组件。
消息可以绑定组件,也可以不绑定组件。没有绑定组件的消息称为全局消息, 而绑定组件的消息称为组件消息。
消息显示组件,也可以指定组件。指定组件的消息组件只能用来显示绑定相同组件的消息。
# 消息分类
根据M18对消息的显示处理,我们将消息分为以下三类:
原生JSF消息
原生JSF消息,需要在页面上指定消息组件才能显示。
通过以下方法添加
//组件消息 MessageUtil.postMessage(String clientId, FacesMessage message) MessageUtil.postMessage(WebUtil.getFacesId('id'), new FacesMessage('Message Summary', 'Message Detail')); //全局消息 MessageUtil.postMessage(FacesMessage message) MessageUtil.postMessage(new FacesMessage('Message Summary', 'Message Detail'));
通知(Notice)类消息
通知类消息在每个页面(视图)都有默认显示。开发者不需要指定显示组件。
如果开发者指定了显示组件, 并且渲染了通知消息,则这个消息不会在经过系统默认显示。
通过以下方法添加:
//MessageUtil.postNotice(String message) MessageUtil.postNotice("Notice message"); //MessageType指定消息类别 //MessageUtil.postNotice(MessageType type, String message) MessageUtil.postNotice(MessageType.INFO, "Notice Message"); //htmlMessage指明消息内容是否是HTML格式 //delayClose指明消息自动关闭前显示的时间 //MessageUtil.postNotice(MessageType type, String message, boolean htmlMessage, int delayClose) MessageUtil.postNotice(MessageType.INFO, "<p>Notice Message</p>", true, 3000)
消息中心消息
消息中心是为Frame视图定义的一种消息显示机制。消息中心用来显示一些具有附加信息的消息,这类消息可以提供一个定制的画面来显示详细的消息内容,并能够与用户交互。
消息中心主要用来显示后台运行过程中产生的
CheckMsg
对象,CheckMsg
一般代表服务器的一种错误信息。消息中心显示的消息对象是
ActionMessage
, 提供了两种交互方式:根据
ActionMessage
对象提供的定位信息,高亮显示对应界面元素。ActionMessage message = ...; FieldLocator locator = ...; message.addLocator(locator);
根据
ActionMessage
对象提供的messageKey
和messageData
,提供详细界面来显示详细的信息。ActionMessage message = ...; message.setMessageKey('core_101'); message.setMessageData(object);
通过
CheckMsg
对象生成ActionMessage
时,会自动根据CheckMsg生成定位信息、messageKey和messageData。消息中心的消息显示:
通过下列方法添加消息中心消息:
//MessageUtil.postMessage(String message) MessageUtil.postMessage("Message"); //MessageType指定消息类型 //autoClose指定消息是否自动关闭 //closable指定消息是否允许用户关闭 //MessageUtil.postMessage(MessageType type, String message, boolean autoClose, boolean closable) MessageUtil.postMessage(MessageType.INFO, "Message", true, true); //生成ActionMessage ActionMessage message = MessageUtil.createMessage(MessageType.INFO, "Message Text", false, true); message.setMessageKey("messageKey"); message.setMessageData(object); MessageUtil.postMessage(message); //根据后台产生的CheckMsg生成消息中心消息 MessageUtil.postMessage(MessageUtil.createMessage(msg));
# 消息中心
每条消息在消息中心显示为两部分:列表视图和详细视图
自定义消息中心的消息显示,是以messageKey为单位,同样messageKey的消息,在一个视图中是同样的显示。
# JavaScript定义消息详细视图
这种方法只能在单个视图生效。
为messageKey="core_101"
定制详细视图。
$.extend(Messager.render, {
'core_101' : {
renderTitle: function(msgCenter, msg) {
return 'Customed title';
},
renderView: function(msgCenter, msg) {
var view = $('<div>Customed detail View here</div>').appendTo(msgCenter.detailView.find('.detail-body'));
return view;
}
}
});
# Java定义消息中心视图
在
app.xml
注册消息视图<message-render> <from-view-id></from-view-id> <message-case> <key>core_201</key> <render></render> <to-view-id>/view/message/fkMessage.xhtml</to-view-id> </message-case> <message-case> <key>core_160503</key> <render></render> <to-view-id>/view/message/fieldGroupMessage.xhtml</to-view-id> </message-case> </message-render>
注意:
可以有多个
message-render
from-view-id
指定某个特定视图,标识当前message-render
仅针对这个特定视图有效。没有设置代表对全局生效。message-case
特指某一类message
key
指messageKey
render
非必填。如果填写,则指定一个class。 这个class必须实现了MessageRender
接口,用来为message生成显示的message/detail内容to-view-id
指定详细视图的JSF文件view-param
指定传给这个视图的参数
编写详细视图页面
消息详细视图就是一个普通的JSF页面。遵循JSF页面编写规则。
<caw:view contentType="text/html" data-width="568px" data-height="278px"> ... <h:body> <div><h5>#{message.info}</h5></div> </h:body> </caw:view>
注意:
- 可以通过在
<caw:view>
上设置data-width
,data-height
来指定详细视图的大小,单位是px
- 在页面上可以通过
#{message}
来获取messageData
- 如果要在页面后台Java Bean/ViewListener上获取
messageData
,可参考
Object messageData = FacesUtil.getContext().getELContext().getLambdaArgument("message");
- 我们提供了一个Bean基类
CawMessageBean
,继承它,则可以直接使用变量msg
获取messageData
- 可以通过在
# 自定义消息显示
M18提供的消息显示组件
# 资料名片(Namecard)开发
资料名片是一种悬浮卡片窗口,用来显示文档的数据。多用于lookup栏位。
# 资料名片触发规则
资料名片在鼠标悬停在HTML元素上时,触发显示。
HTML元素符合以下条件,就能够触发资料名片:
元素具备属性
data-lookup
,指明lookupType(定义在stSearch.xml
中)<input id="text1" type="text" data-lookup="employee"/>
元素能够获取
lookup
数据id
信息增加
lookup
数据的方法:通过
data-id
属性在HTML元素上指定<input id="text1" type="text" data-lookup="employee" data-id="5"/>
通过JavaScript,监听事件
active.nc
,在事件响应中动态增加id
数据。$('#text1').on('active.nc', function(event) { $(this).data('id', 5) ; });
# 资料名片页面配置
资料名片的显示依赖于两部分:数据和显示模板
资料名片数据包含三部分:
- 显示在名片上的文档数据(lookup读取的所有数据)
- 文档所包含的附加文件
- 显示在名片上的附件链接
开发者/用户可以设定“显示在名片上的文档数据”以及“显示在名片上的附加链接”
资料名片显示模板是显示名片数据的一种布局/显示方案。
开发者可以创建新的模板,开发者/用户可以为指定lookup
选择模板。
注意:
- M18平台为所有
lookup
设定了默认的“显示数据”,开发者可以在此基础上增加“显示数据”,或者重新定义“显示数据”。- M18平台提供了默认的资料名片显示模板,开发者可以设置默认模板的属性,也可以改用其他模板。
# 配置默认数据和模板
<stSearch ...>
<namecard height="400" widht="250" src="" imgCode="photo" extendDefault="true">
<field name="code" mess="core.code">
<link ...>...</link>
</field>
<link menuCode="employee" id="empId" text="Go to employee" href="">
<param name="beId" field="beId" value=""></param>
</link>
<option template="templateCode" templateOption="{}"></option>
</namecard>
</stSearch>
namecard
属性介绍
属性 | 说明 |
---|---|
height | 资料名片显示高度 |
width | 资料名片显示宽度 |
imgCode | 为默认模板设置一个图片显示,指定的是一个数据栏位,默认模板根据这个栏位去获取图片数据。 |
src | 指定一个JSF视图文件。用这个指定的视图文件作为资料名片的显示。不使用默认模板 |
extendDefault | 默认为true。 指当前namecard配置是否基于默认模板数据。 为true时,以默认模板为基础,增加field 和link ;为false时,配置为全新模板数据,不会添加默认模板数据。 |
field
属性介绍
field
用来给资料名片增加显示栏位。
属性 | 说明 |
---|---|
name | lookup 栏位名称 |
mess | 资料名片显示的标签(messCode ,系统根据messCode 获取显示文本内容) |
field
可以设置一个link
,当设置了link
后,用户在界面上点击显示的field
时,将根据这个链接跳转。
link
属性介绍
link
用来给资料名片增加附加链接
属性 | 说明 |
---|---|
menuCode | 链接关联的系统菜单 |
id | 传入到对应系统菜单的文档id , 这里设置的是一个lookup 数据栏位,系统根据这个栏位去获取传入id 的真实值 |
text | 显示在资料名片上的链接文本,这里填入的是messCode |
href | 如果关联的是外部链接,则填写外部链接url |
needBe | 是否跳转到链接时,传入当前beId |
link
可以通过param
设置参数
param
属性介绍
属性 | 说明 |
---|---|
name | 传入链接的参数名称 |
value | 传入链接的参数值,String 类型 |
field | 设置参数值来源于lookup 的某个数据栏位 |
# 为资料名片指定模板
开发者通过option
标签,可以更改资料名片的显示模板。 属性如下:
属性 | 说明 |
---|---|
template | 设置资料名片采用的模板的编号(见资料名片模板的开发 ) |
templateOption | 设置选择模板的配置数据, json 格式。 |
# 资料名片显示页面可用配置
在任意模板,或者为资料名片定制的JSF视图中,可以通过以下方法,设置视图显示大小
<caw:namecardOption height="500" width="200" />
# 资料名片模板的开发
资料名片模板用来让开发者/用户运用到某个lookup
的资料名片上。
开发者在stSearch.xml
中用namecard
来配置资料名片。见配置默认数据和模板
用户通过【自定义查询】下的资料名片来配置资料名片。见用户自定义资料名片
# 用户自定义资料名片
从上图可知,图片设置、数据栏位设置、附加链接设置、模板设置都和开发者配置一一对应。这里的模板属性设置界面,是由模板开发者提供的。
# 开发资料名片模板
资料名片模板只负责资料名片数据的显示,可以存在配置项供用户/开发者进行配置。如果提供配置项供用户配置,则必须提供配置界面。
开发资料名片模板只需要提供模板页面,模板配置,并在app.xml
中注册就可以了。
# 资料名片模板的注册
<template>
<namecard code="verticalNamecard" description="core.defaultNamecard">
<page>view/namecard/verticalNamecard.xhtml</page>
<setting option="com.multiable.web.config.VerticalNamecardOption">
/view/namecard/verticalTemplateSetting.xhtml
</setting>
</namecard>
</template>
namecard
属性说明
属性 | 说明 |
---|---|
code | 模板的唯一标识,M18平台唯一 |
description | 模板名称,是messCode |
lookupType | 指定lookupType ,限制模板只能使用于哪些lookup ,可设置多个,以, 分隔 |
page
用来指定模板视图文件
setting
用来指定配置视图文件。如果模板不支持配置项,则不需要setting
。其属性说明如下:
属性 | 说明 |
---|---|
option | 用来指定配置项数据的Java对象(必须是简单的JAVA对象,具有set/get, 能够通过fastjson直接抓成json) |
# 模板视图的编写
模板视图也是一个JSF视图文件,所以遵循所有JSF规范。没有任何限制。
在这个视图文件上,可以使用许多已经预置的环境变量。如下表
表达式 | 返回值 | 说明 |
---|---|---|
#{namecard} | NamecardInfo | NamecardInfo 包含了资料名片所有的设置信息 |
#{option} | 在注册时,setting 中option 属性指明的Java对象 | 包含用户配置的模板配置项数据 |
#{namecardBean} | NamecardBean | NamecardBean 是一个SessionScope 的Java Bean。 内置许多方法获取资料名片的数据。 |
#{namecard.fields} | List<NamecardField> | 返回所有显示在资料名片上的数据栏位 |
#{namecard.links} | List<NamecardLink> | 返回所有显示在资料名片上的附件链接信息 |
#{namecardBean.imageCode} | String | 资料名片需要显示的图片key |
#{namecardBean.getText(field)} | String | 根据NamecardField 对象field 来获取需要显示在界面上的文本内容 |
#{namecardBean.isFieldRendered(field)} | boolean | 根据NamecardField 对象field ,判断此数据是否对当前用户显示(如果当前用户没有该项数据的查看权限,则不会显示) |
#{namecardBean.getLinkUrl(link)} | String | 根据NamecardLink 对象link 获取对应的链接url |
#{namecardBean.isSystemLink(link)} | boolean | 判断NamecardLink 对象link 是否是系统内部链接 |
#{namecardBean.isLinkRendered(link)} | boolean | 根据NamecardLink 对象link ,判断此链接是否对当前用户显示(当前用户没有权限,则不会显示) |
#{namecardBean.openSupport} | boolean | 判断当前资料名片是否支持打开文档单据功能 |
#{namecardBean.printSupport} | boolean | 判断当前资料名片是否支持打印文档单据功能 |
#{namecardBean.attachSupport} | boolean | 判断当前资料名片是否支持显示文档附件 |
#{namecardBean.attachment} | SqlTableIteratorModel | 返回当前资料名片所有的文档附件。SqlTableIteratorModel 支持JSF <c:foreach> ,<ui:repeat> 遍历标签。 |
# 配置界面的编写
配置说明
<setting option="com.multiable.web.config.VerticalNamecardOption">
/view/namecard/verticalTemplateSetting.xhtml
</setting>
option
属性设定的class
必须支持JSON转换: 支持fastjson的JSON.toJSONString(optionObject)
和JSON.parse(String, optionClass)
。建议写成POJO。如果是复杂的JAVA对象,请编写JSONserializer
和JSONDeserializer
。
页面编写说明
在编写配置视图时,视图内容必须包含在<ui:composition></ui:composition>
之间。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition>
<!--资料名片模板配置视图 -->
</ui:composition>
</html>
在配置视图上可以使用JavaBean。 但是Bean必须实现TemporaryBean
接口, TemporaryBean
接口中getTemplateKey()
返回模板的code
。
# 快捷键(Shortcut)开发
# 概述
M18平台有统一的快捷键支持接口。
M18快捷键都具备以下属性:
code
: 全系统唯一。建议开发者在增加快捷键时,以APP的编号作为前缀。key
: 快捷键的触发键。 组合键以空格分开。比如ctrl shift f
mess
: 快捷键的描述, 是一个messCode
action
: 快捷键的触发动作。
# 组件上使用快捷键
目前M18平台上有三种组件支持快捷键:commandButton
、toolbar(actionItem)
、dropMenu(menuItem)
。 快捷键触发时的动作,相当于鼠标点击对应组件。
使用方法:
在组件上直接使用以下属性:
<caw:commandButton id="download" shortcut="ctrl alt t" ...></caw:commandButton>
<caw:toolbar>
<caw:actionItem action="refresh" shortcut="ctrl shift r" shortcutCommand="module.refresh">
</caw:actionItem>
</caw:toolbar>
属性说明
属性 | 说明 |
---|---|
shortcut | 必填,快捷键的触发键 |
shortcutCommand | 快键键的code . 如果没有填写,系统会取commandButton 的id , toolbar(actionItem) 的id +action , dropmenu(menuItem) 的id + event 。 |
shortcutMess | 快捷键的mess 。如果没有填写,系统会自动获取对应组件的label 。 |
注意:
这里shortcutMess所使用的
mess
必须是webMess
()。
# 页面上使用快捷键
在页面上可以为特定命令指定快捷键。
使用方式:在<form></form>
使用<caw:shortcut>
来增加快捷键:
<h:form>
<caw:shortcut key="ctrl shift i" event="test.shortcut" text="messCode" listener="#{xxxBean.doShortcutAction}"></caw:shortcut>
</h:form>
属性说明:
属性 | 说明 |
---|---|
key | 快捷键的触发键(组合)。 |
event | 快捷键的code |
text | 快捷键的shortcutMess |
listener | 绑定快捷键的响应函数 |
# JavaScript中使用快捷键
在每个JSF视图上,都有一个JavaScript
对象:window.shortcut
。可以通过方法registerShortcut(code, key, handler, mess)
来注册快捷键。
window.shortcut.registerShortcut('test.shortcutCode', 'ctrl shift t', function() {
alert('test shortcut');
}, 'messCode')
四个参数说明
参数 | 说明 |
---|---|
code | 快捷键的code |
key | 快捷键的触发键 |
handler | 快捷键的响应函数 |
mess | 快捷键的shortcutMess |
# 系统提醒(Alert)的开发
# 系统提醒功能相关视图
- 系统工具栏, 系统提醒入口
系统通知列表
系统提醒弹出通知窗口(系统右下方)
系统提醒详细视图
# 系统提醒常用接口
系统提醒常用API只有一个——发送系统提醒
//SysAlertUtil.java
public static void sendAlert(SysAlertObj alert, Collection<Long> recipient){...}
SysAlertObj
是系统提醒的描述对象基类。 每一种系统提醒都会提供对应的SysAlertObj
。
参数说明
参数 | 说明 |
---|---|
alert | SysAlertObj 对象,发送的“提醒数据”。 |
recipient | 提醒的接收者(系统用户id 集合)。 |
# 系统提醒开发
如上所述,系统提醒包含的内容:提醒Java对象(SysAlertObj)、提醒列表视图和弹出窗视图、提醒的详细视图。
所以开发者在开发新的“系统提醒”时,也需要提供上述三部分定义。
# 定义系统提醒Java对象
每一中系统提醒,都有一个对应的Java对象,用来描述提醒的具体数据。
这个Java对象必须继承于SysAlertObj
。以下四个方法要留意
//以下两个方法必须实现。CawJsonSerializable接口方法。
public void restoreJsonData(String jsonData) {
... //根据jsonData 还原系统提醒Java对象
}
public String saveJsonData() {
...//将当前系统提醒Java对象,转换成Json格式描述的字符串
}
//以下两个方法,可选(具体作用见后面说明)
public String getMessageText() {
return ""; //描述当前提醒的基本信息 (不支持html格式)
}
public String getDetailText() {
return ""; //描述当前提醒的详细信息 (不支持html格式)
}
注意
saveJsonData
将当前系统对象Java对象转成Json字符串,但是SysAlertObj
的基本变量:id
、type
等(定义在SysAlertObj.java中)不需要保存在这个JSON字符串中
MessageAlert实例
@SysAlert(value = "com.multiable.alert.message", rendererType = "com.multiable.alert.message")
public class MessageAlert extends SysAlertObj {
public static final String TYPE = "com.multiable.alert.message";
private static final long serialVersionUID = -1729512694173799571L;
String message = "";
String detail = "";
public String getMessage() {
return message;
}
public String getDetail() {
return detail;
}
public void setMessage(String message) {
this.message = message;
}
public void setDetail(String detail) {
this.detail = detail;
}
/** overwrite methods **/
@Override
public void restoreJsonData(String jsonData) {
JSONObject jsonObj = (JSONObject) JSON.parse(jsonData);
this.setMessage(jsonObj.getString("message"));
this.setDetail(jsonObj.getString("detail"));
}
@Override
public String saveJsonData() {
JSONObject jsonObj = new JSONObject();
jsonObj.put("message", getMessage());
jsonObj.put("detail", getDetail());
return jsonObj.toJSONString();
}
@Override
public String getMessageText() {
return getMessage();
};
@Override
public String getDetailText() {
return getDetail();
};
}
# 定义系统提醒视图渲染器
系统提醒视图渲染器负责定义系统提醒的显示效果。由于系统提醒的视图存在两种: 列表视图(弹出窗口共用)和 详细视图。 所以渲染器类中也包含两种视图渲染的接口。
public interface SysAlertRender {
public void encodeAlert(FacesContext context, SysAlertItem alert);
public void decodeAlert(FacesContext context, SysAlertItem alert, String action);
public void encodeAlertView(FacesContext context, SysAlertItem alert);
public default String encodeAlertTitle(FacesContext context, SysAlertItem alert) {
return WebUtil.encodeHtmlText(alert.getAlertObj().getMessageText());
};
public default String encodeAlertContent(FacesContext context, SysAlertItem alert) {
return WebUtil.encodeHtmlText(alert.getAlertObj().getDetailText());
};
}
接口说明
encodeAlert
用来渲染提醒列表视图decodeAlert
用来响应提醒列表视图上的事件encodeAlertView
用来渲染提醒详细视图encodeAlertTitle
非必要接口。 用来简化提醒的显示。 以一个String
(支持html格式)来显示系统提醒的标题。encodeAlertContent
非必要接口。用String
(支持html格式)来描述系统提醒的具体内容。
# 要点说明
- 在提醒列表视图(弹窗)中,增加按钮(或其他HTML元素)支持事件(只支持click事件):
只需要在渲染的html元素中,增data-action
属性,就可以支持click事件
<button type="button" data-action="myAction" data-dismiss="true">
myAction
</button>
注意:
- 这里的
data-action
会作为渲染器接口decodeAlert
第三个参数。data-dismiss
可选, 当data-dismiss="true"
时,用户点击按钮后,会自动关闭提醒列表视图,否则不会关闭。
AbstractAlertRender
的使用
为了简化开发者的开发,M18提供了一个渲染器的基类AbstractAlertRender
。 开发者可以选择使用它,也可以选择重新编写渲染器。
AbstractAlertRender
提供的列表视图布局如下图
包含四个部分:图标区、标题区、内容区、时间戳。
- 图标区,通过覆写方法
getIcon
来定制
protected ImageObject getIcon(SysAlertItem item) {
return null;
}
- 标题区,可以通过以下三种方式来定制
- 通过定义系统提醒Java对象中的方法
getMessageText()
来指定标题内容。不支持html格式 - 通过定义渲染器中的方法
encodeAlertTitle
来指定标题内容。支持html格式 - 通过覆写
AbstractAlertRender
的方法encodeTitle
来修改标题区渲染。
- 通过定义系统提醒Java对象中的方法
- 内容区,可以通过以下三种方式来定制
- 通过定义系统提醒Java对象中的方法
getDetailText()
来指定内容区的文本。 不支持html格式、 - 通过定义渲染器中的方法
encodeAlertContent
来指定内容区。 支持html格式。 - 通过覆写
AbstractAlertRender
的方法encodeItemContent
来修改内容区渲染
- 通过定义系统提醒Java对象中的方法
- 时间戳,不支持开发者扩展。
系统提醒详细视图的编写
开发者可以通过实现渲染器的
encodeAlertView
方法来定制提醒的详细视图。如果开发者选择使用
AbstractAlertRender
, 可以通过独立JSF视图来编写详细视图。步骤如下:覆写
getViewUrl
,来指定一个JSF视图protected String getViewUrl(SysAlertItem item) { return "/view/alert/messageAlert.faces"; }
编写JSF视图,在仕途上可以使用
#{alertItem}
来获取SystemAlertItem对象。<h:form> <p>ID : #{alertItem.id}</p> <p>Title : #{alertItem.alertObj.messageText}</p> <p>Detail : #{alertItem.alertObj.detailText}</p> </h:form>
# 注册系统提醒Java对象和渲染器
系统提醒的两大部分: Java对象和渲染器,都需要在M18平台注册。渲染器单独注册。 系统提醒Java对象在注册时,需要指定一个注册过的渲染器。
注册方法一: 在faces.xml
中通过xml配置注册
<sysalert-config>
<rendererType name="com.multiable.alert.message">
com.multiable.web.sysalert.render.MessageAlertRender
</rendererType>
<sysalert name="com.multiable.alert.message">
<object>className</object>
<renderer>com.multiable.alert.message</renderer>
</sysalert>
</sysalert-config>
注意:
所有的配置必须在
sysalert-config
之间。注册渲染器
<rendererType name="code">className</rendererType>
这其中的
code
必须是系统唯一的。这里的
className
必须是一个渲染器(SysAlertRender
)的实现注册Java对象
<sysalert name="code"><object>className</object></sysalert>
这其中的
code
也必须是系统唯一的
className
必须是一个SysAlertObj
在
<sysalert>
中,需要通过<renderer>renderCode</renderer>
来指定渲染器。这里的
renderCode
必须通过是已经注册过的渲染器。
注册方法二: 在Java代码中通过注解注册
渲染器通过渲染器Java代码来注册
@SysAlertRenderer(rendererType = "com.multiable.alert.message") public class MessageAlertRender extends AbstractAlertRender { ... }
在提醒渲染Java类中通过
@SysAlertRenderer
来注册, 属性rendererType
就对应xml配置中的name
,系统唯一。系统提醒Java对象通过Java代码来注册
@SysAlert(value = "com.multiable.alert.message", rendererType = "com.multiable.alert.message") public class MessageAlert extends SysAlertObj { ... }
在系统提醒Java对象类中通过
@SysAlert
来注册。属性value
对应xml配置中name
,系统唯一;属性rendererType
用来指定已经注册的渲染器。
# 系统链接(Link)的规则
# 系统链接规则
http://192.168.10.214:8080/jsf/app.faces#employee/view/module/employee/870f5fee?id=63
M18系统中,一个链接地址包含的内容:
协议和服务器信息。 (
http://192.168.10.214:8080
)M18项目路径。(
/jsf
)M18系统资源路径,相对于项目路径。(
/app.faces
)访问模块信息。(
#menuCode/resourcePath/randomKey
)menuCode
:在navmenu.xml
配置的每个menu
的code
resourcePath
: 模块的路径。 以employee
为例,未见路径为view/module/employee.xhtml
,所以其resourcePath
为view/module/employee
randomKey
由系统生成参数(
?id=63)
)
# 获取系统链接
获取指定Frame的系统链接,可通过以下API获取
WebUtil.getFrameLink(String menuCode, Map<String, String> params);
# 链接参数的解析
一个Frame视图如果要支持参数,则必须在视图java代码中对参数进行获取和解析。一般情况下,我们在ViewListener的initalized
接口中处理。
# 推送机制的使用
推送(PUSH)是服务器主动向前端发送消息的一种方法。 M18平台封装了推送机制,并提供了一套友好的API。 开发者根据功能需求使用。
推送,涉及到前端后台的交互,整个过程如下:
- 前端发起一个到后端的连接
- 后端通过一个服务类(Java)来监听这个连接,并处理传送的消息。
- 前端或者后台关闭这个连接。
# 前端
在JavaScript代码中, 开发者可以通过myView.createWebSocket(name, option, params)
创建一个websocket连接。
name
: 与后台服务的定义对应。option
: 一个JavaScript对象, 支持onmessage
、onerror
、onclose
, 值都是JavaScript函数。params
: 传入到后台服务的参数。
# 后台
- 服务类,必须继承于
CawWebSocket
- 必须使用注解
@ServerEndpoint
声明, 其value
属性与前端的name
属性一致 - 在
onOpen
方法中,可以将前端传入的参数保存起来
# 实例
@ServerEndpoint(value = "/sysAlert", encoders = { SysAlertEncoder.class })
public class SysAlertWebSocket extends CawWebSocket {
...
public long getUid() {
Long id = (Long) getProperty("uid");
if (id == null) {
return 0;
}
return id;
}
@Override
public void onOpen(Session session, EndpointConfig ec) {
// TODO Auto-generated method stub
String userId = getRequestParam(session, "userId");
if (userId != null) {
setProperty("uid", ConvertLib.toLong(userId));
} else {
return;
}
}
@Override
public String getSocketKey() {
long id = getUid();
if (id > 0) {
return String.valueOf(id);
}
return null;
}
@Override
public boolean isSupportCluster() {
return true;
}
...
}
var option = {};
option.onmessage = function(event) {
...
};
option.onerror = function(event) {
...
};
this.webSocket = myView.createWebSocket('sysAlert', option, {userId : uid})
# 常用接口
# 推送消息到前端
推送API都定义在在WebSocketUtil.java
中,常用的接口如下:
public static void sendMessage(String webSocket, String message, Predicate<CawWebSocket> filter) {
...
}
public static void sendObject(String webSocket, Object message, Predicate<CawWebSocket> filter) {
...
}
public static void sendText(String webSocket, String message, Predicate<String> keyPredicate) {
}
注意:
- 参数
webSocket
就是@ServerEndpoint
中分配的value
值- 第三个参数
filter
,keyPredicate
都是用来指定将消息传给哪些websocket.- 第二个参数,就是传送的消息。传送的消息最后都是文本形式。如果使用
sendObject
则在注册时@serverEndpoint
应该提供encoders
属性,使用encoder将Object转成文本。- 特别注意: 只有
sendText
支持集群环境。所以如果推送机制涉及到多个集群服务器,那么必须使用sendText
来推送消息。
# 主界面插件的开发
在M18主页上,有两种插件形式的扩展,支持开发者新增的。
spotlight
: 在系统工具栏上,用户通过搜索框,触发spotlight插件,spotlight
根据用户输入的关键字,在M18上进行搜索,然后将结果显示出来。widget
:桌面小插件。用来显示某个特定的功能视图。开发新增widget
后,用户在自定义桌面时,可以挑选开发者的widget
,进行配置,并放置到桌面上显示。
# Spotlight插件
spotlight
插件负责两项职责:关键字搜索和结果显示。
因此spotlight
插件也包含这两部分开发。
# 关键字搜索
spotlight
插件通过一个SpotlightHelper
接口类来实现关键字搜索,通过CawSpotlight
对象来实现参数传递和搜索结果的返回。
public interface SpotlightHelper extends Serializable {
public void doSearch(FacesContext context, CawSpotlight spotlight);
public void spotlightInit(FaceletContext ctx, CawSpotlight spotlight, UIComponent container);
}
注意:
spotlightInit
方法只会在当前spotlight插件初始化时调用一次。doSearch
在每次用户进行关键字搜索时,都会调用CawSpotlight
包含当前插件的所有信息,以及当前搜索环境(包括搜索关键字)CawSpotlight.setRendered(boolean rendered)
可用来设置当前插件是否显示。比如当搜索结果为空的时候,可以使用这个方法隐藏插件的显示。
设置显示变量和回传搜索结果
在doSearch
中,插件需要进行关键字搜索,保存结果,以及设置一些其他环境变量,以便插件渲染时使用。
保存结果,或者设置其他环境变量,通过方法CawSpotlight.setData(String key, Object obj)
来处理。
public void doSearch(FacesContext context, CawSpotlight spotlight) {
String searchKey = spotlight.getSearchKey();
List<NavMenuItem> items = new ArrayList<>();
... // do search with searchKey
spotlight.setData("searchResult", items); //Set result to spotlight
spotlight.setRendered(items.size() > 0);
}
在显示页面上,可以通过EL表达式#{content.data.key}
直接访问这里设置的变量,这其中的key
就是上述设置变量时用到的key
。
# 搜索结果显示
spotlight
插件显示,是通过一个独立的JSF视图文件来定义的。页面遵循JSF视图开发规则。但要注意,这个视图只是一个插件视图,所以文件上不能有<view>,<body>,<form>
标签,并且视图必须在<ui:composition></ui:composition>
之间。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:caw="http://www.cloud-at-work.com">
<ui:composition>
<h:outputScript name="menu.min.js" library="js/spotlight"></h:outputScript>
<h:outputStylesheet name="menu.min.css" library="css/spotlight"></h:outputStylesheet>
<h5 class="container spotlight-title">#{caw:mess('spotlight.menu')}</h5>
<caw:menuSpotlight value="#{content.data.searchResult}"></caw:menuSpotlight>
</ui:composition>
</html>
# Spotlight注册
spotlight
需要在app.xml
中注册。
<app>
<spotlight name="menu"
mess="share.spotlight.menu"
src="/spotlight/menu.xhtml"
helper="com.multiable.web.spotlight.MenuSpotlight">
</spotlight>
</app>
spotlight
属性说明
属性 | 说明 |
---|---|
name | spotlight 标识,系统唯一 |
mess | spotlight 描述, messCode 类型 |
src | spotlight 插件显示文件 |
helper | spotlight 插件搜索处理类 |
# Widget插件
widget
插件与spotlight
类似,区别在于widget
不需要用户触发,当用户将widget
配置在桌面上之后,每次打开M18系统,桌面上都会对应widget
插件。
widget
插件包含两个视图: 用户配置视图(可以没有)和显示视图
# widget显示视图
M18允许开发者提供一个Java类来为显示视图准备数据,类似spotlight
的搜索动作。
这个Java类,必须实现接口WidgetHelper
public interface WidgetHelper extends Serializable {
public void prepareWidget(FacesContext context, CawWidgetContent content);
public void widgetInit(FaceletContext ctx, CawWidgetContent content, UIComponent container);
}
注意:
widgetInit
只在widget
初始化时调用一次prepareWidget
则在每次视图显示前,调用,以为视图提供最新的数据CawWidgetContent
可以获取当前widget
的所有信息。
设置环境变量,通过方法CawWidgetContent.setData(String key, Object obj)
来设置变量,页面通过EL表达式#{content.data.key}
来获取。
显示视图的编写,与spotlight
一致。文件上不能有<view>,<body>,<form>
标签,并且视图必须在<ui:composition></ui:composition>
之间。
# widget配置视图
配置视图需要提供一个描述配置信息的Java类,这个Java对象必须能够通过fastjsonJSON.toJSONString
和JSON.parseObject
进行转换。所以建议是POJO对象。或者,这个JAVA类实现了CawJsonSerializable
接口。
在显示视图上,可以通过#{content.option}
获取当前widget
的配置数据对象。
配置视图文件要求与widget
显示视图要求一致。文件上不能有<view>,<body>,<form>
标签,并且视图必须在<ui:composition></ui:composition>
之间。
配置视图文件中可以使用Java Bean。但是这个JavaBean必须实现WidgetOptionBean
接口
# widget注册
widget
需要在app.xml
中注册,方能生效。
<app>
<widget name="linkWidget" mess="widget.linkWidget"
src="/widget/linkWidget.xhtml"
helper="com.multiable.web.widget.LinkWidget"
width="6" height="5" >
<icon name="linkWidget.jpg" library="img/widget"></icon>
<description mess="widget.linkWidget.description">
<![CDATA[<p>A widget to show a link with an image!</p>]]>
</description>
<option src="/view/widget/linkWidgetOption.xhtml">
com.multiable.web.widget.LinkOption
</option>
</widget>
</app>
widget
属性介绍
属性 | 说明 |
---|---|
name | widget 唯一标识,系统唯一 |
mess | widget 描述文本。 messCode 类型 |
src | widget 显示视图文件 |
helper | widget 数据处理Java类 |
width | widget 默认所占栏数。(总共12栏) |
height | widget 默认所占行数。(行高30px) |
<icon/>
用来给widget
指定一个图片标识。
<description></description
用来指定描述widget
的文本。 通过属性mess
可以指定一个messCode
文本。 也可以通过description
中间文本来指定。 支持html格式。
<option src="resourcePath">className</option>
用来指定widget
的配置信息。 src
属性指明配置视图文件路径。className
指定widget
的配置数据Java对象。
# 皮肤开发
# 概述
# 主题分类
M18平台主题分为“系统主题”和“用户主题”。
系统主题,只允许开发者增加。用户不可修改。
用户主题,每个用户可以有一个用户主题,用户可以以当前主题为模板,调整主题的色板,从而生成自己的个性化主题。
# 用户自定义
用户自定义主题,就是用户定义“主题色板”的一个过程。
系统提供了一套”主题色板“,包含一系列颜色变量,用户可以给每一个颜色变量分配一个颜色。
这一系列颜色变量包含
M18系统基础颜色变量
系统基础颜色变量包含以下五种:
Primary
、Info
、Warning
、Danger
、Success
系统也提供了额外的颜色变量,用来控制组件的主题色。比如
系统文本颜色
、必填栏位标签颜色
等。特殊页面颜色变量
M18平台为系统主页提供了颜色变量,让用户来调整主页主题色彩。
开发者提供特定页面的颜色变量。
M18平台允许开发者为特定页面编写颜色变量。类似系统特殊页面颜色变量。
# 主题CSS文件的编写
只有希望页面能够跟随主题切换而变换色彩的时候,才需要编写主题css文件.
# 主题CSS文件命名和路径规则
- 主题css 文件必须放在
WebContent/resources/themes/*/
下面。(*
为任意路径) - 主题css文件名必须是
*.dynamic.css
. (*
为任意文件名)
# 主题CSS文件编写语法
//set {app-header-background} = {primary}
set {app-logo-background} = {app-header-background + 10%}
set {app-header-color} = #fafafa
set {app-header-active-background} = {app-header-background + 10%}
//set {app-menu-background} = {darkGray-}
set {app-menu-color} = {lightGray}
set {app-menu-active-background} = {app-menu-background + 40%}
set {app-menu-active-color} = {app-menu-color - 50%}
//set {app-tab-background} = {body-bg}
set {app-active-tab} = {warning * 0.5}
set {app-notice-tab} = {primary * 0.3}
//set {app-homepage-background} = {body-bg}
.theme-{theme} .main-container,
.theme-{theme} .main-container:before {
background-color: {app-homepage-background};
}
.theme-{theme} #header.header-container {
background-color: {app-header-background};
}
.theme-{theme} #header.header-container header.top-header .logo {
background-color: {app-logo-background};
}
.theme-{theme} #header.header-container header.top-header .header-link {
color: {app-header-color};
}
定义一个变量:
set {变量名} = 变量值
变量值
可以是一个固定值,也可以引用其他变量的值。如果是引用其他变量的值,必须使用{其他变量}
运算操作符:
-
,+
,*
-
: 使颜色变淡, 后面接a%
,a
在0~100
之间+
: 使颜色加深,后面接a%
,a
在0~100
之间*
: 是颜色变透明,后面接一个float
数值使用变量
在css文件中,使用变量
{变量名}
# 主题CSS文件可使用的变量
- 系统基础颜色变量包含以下八种:
primary
、info
、warning
、danger
、success
、gray
、lightGray
、darkGray
以上每种颜色变量,都具备两种渐变色变量,以primary
为例,存在primary-
,primary+
两种变量。
Bootstrap颜色变量(100+各变量)
参考文件
WebContent/themes/css/caw.variable.dynamic.css
# 如何增加系统主题
开发者如果要增加一套系统主题,只需要在faces.xml
配置一套主题色板即可。
<faces>
<theme-config>
<theme code="blue" description="theme.blue">
<color name="primary" value="#2D7DD2"></color>
<color name="info" value="#3BAFDA"></color>
<color name="warning" value="#EEB902"></color>
<color name="danger" value="#F45D01"></color>
<color name="success" value="#97CC04"></color>
<color name="app-homepage-background" value="#f9f9f9"/>
<token key="input-bg-disabled" value="{lightGray}"></token>
</theme>
</theme-config>
</faces>
配置一套主题色板,最少只需要配置五种基本色彩就可以了。
theme
属性说明
属性 | 说明 |
---|---|
code | 主题唯一标识,系统唯一 |
description | 主题描述文本,messCode 类型 |
<color>
来设置颜色变量, name
是变量名, value
是变量值。
<token>
用来设置颜色变量的依赖关系。key
是变量名, value
是一个颜色变量表达式
# 如何让页面支持“用户自定义主题”
只有开发者的页面上定义了额外的变量,而且期望用户能够单独设置页面主题时,才需要处理。
# 注册
在faces.xml
中注册主题变量/css文件/demo页面
<faces>
<theme-config>
<specification code="app"
description="theme.spec.app"
demo="/view/theme/appDemo.xhtml">
<token key="app-header-background" value="{primary}"
description="theme.app.header.bg"/>
<token key="app-tab-background" value="{body-bg}"
description="theme.app.tab.bg"/>
<token key="app-menu-background" value="{darkGray-}"
description="theme.app.menu.bg"/>
<token key="app-homepage-background" value="{body-bg + 5%}"
description="theme.app.homepage.bg"/>
<resource name="theme/app.dynamic.css" library="themes/css"/>
</specification>
</theme-config>
</faces>
specification
用来注册,属性说明如下:
属性 | 说明 |
---|---|
code | 系统唯一标识 |
description | 描述文本,messCode 类型 |
demo | 用作实例的视图文件 |
token
用来定义变量,属性说明如下:
属性 | 说明 |
---|---|
key | 变量名 |
value | 默认值,必须与系统提供的变量关联起来 |
description | 描述文本,messCode 类型 |
resource
用来指定主题css文件。
# CSS文件编写和使用
主题css文件就是一个dynamic.css文件,编写方法见主题CSS文件的编写
# demo页面编写
demo页面,只是一个JSF视图,遵循JSF视图开发规则。但是文件上不能有<view>,<body>,<form>
标签,并且视图必须在<ui:composition></ui:composition>
之间。
# 扩展现有功能
# 扩展模块
扩展模块指在现有的模块功能上,扩展新的功能。
# 扩展注册
扩展模块需要在app.xml
中注册。
<app>
<extension>
<view code="formComp" app="caw" filter="" type="frame">
<controller></controller>
<page>/extension/extensionTest.xhtml</page>
<listener></listener>
</view>
<view code="employee" app="caw" filter="" type="frame" apDebug="true">
<page>/extension/extensionEmployee.xhtml</page>
</view>
</extension>
</app>
<view>
用来指定扩展的目标视图。属性说明如下
属性 | 说明 |
---|---|
code | 目标视图的编号。Frame视图上,code 默认是navmenu.xml 上配置的code |
app | 目标视图所在的App编号。 |
filter | 用来过滤视图的Java类。必须实现ViewFilter 接口。 |
type | 视图类型。目前有frame ,dialog ,`namecard |
ViewFilter
接口:
public interface ViewFilter extends Serializable{
public boolean isViewIncluded(ViewExtension extension, ViewType type, String code);
}
这个接口用来判断,视图(ViewType=type
,menuCode=code
)是否要运用这个扩展extension
, 返回true
则意味着扩展对这个视图生效。
<controller>
用来给指定视图,扩展视图控制器。 扩展视图控制器的Java类,必须根据View类型继承于以下class的其中一个:ViewControllerWrapper
、DialogControllerWrapper
、FrameControllerWrapper
、ModuleControllerWrapper
<page>
用来指定扩展视图文件。<listener>
用来给指定视图,增加ViewListener
# 扩展标签和新增组件
在扩展标签和新增组件前,开发者必须指定一个原始图上的组件作为扩展目标。
<caw:extendComponent target="targetId", action="before|after|prepend|append"></caw:extendComponent>
<caw:extendComponent>
用来确定在原始图上target
组件的那个位置进行扩展:
- before : 在
target
组件的前面扩展。新扩展的元素与target
在同一级(同一个父组件) - after:在
target
组件的后面扩展。 - prepend:扩展为
target
组件的子组件,并且在最前面增加扩展。 - append: 扩展为
target
组件的子组件,并且在最后面增加扩展。
<caw:extendComponent target="code" action="before">
<!--在code组件前面,增加一个按钮-->
<caw:commandButton value="Extend Button"></caw:commandButton>
</caw:extendComponent>
<caw:extendComponent target="button" action="prepend">
<!--给button组件增加一个事件-->
<caw:ajax event="click" listener="xxbean.doAction"></caw:ajax>
</caw:extendComponent>
# 扩展/修改组件属性
同扩展标签/增加组件一样,修改组件属性也需要通过<caw:extendComponent>
来指定组件,但是不再需要action
属性。
//原视图
<caw:inputText tableName="employee" columnName="code" style="border: 1px solid blue"></caw:inputText>
//扩展视图
<caw:extendComponent target="employee_code">
<caw:setProperty name="style" value="background-color: red;"
updateType="extend"/>
</caw:extendComponent>
通过<caw:setProperty>
来修改目标组件属性,其属性说明如下
属性 | 说明 |
---|---|
name | 修改/扩展的属性名 |
value | 属性值 |
updateType | 对原组件属性的修改方式:extend 和replace 。 |
对于style
,styleClass
属性:
以上述代码为例,组件employee_code
的style
属性会同时具备两种设定。显示效果为:
如果将扩展代码修改为
<caw:extendComponent target="employee_code">
<caw:setProperty name="style" value="background-color: red;"
updateType="repolace"/>
</caw:extendComponent>
则组件employee_code
在原视图文件中的设定会被清除。效果为:
对于其他属性而言, 如果扩展属性的value
是一个literalValue
(不是EL表达式), 则会直接覆盖原始视图上组件的属性(采用的updateType=replace
)。只有扩展属性的value
是一个EL表达式的时候,updateType属性才会效果。
//原始视图
<caw:inputText tableName="employee" columnName="code" maxlength="10"/>
//扩展视图
<caw:extendComponent target="employee_code">
<caw:setProperty name="style" value="background-color: red;"
updateType="repolace"/>
<caw:setProperty name="maxlength" value="15"/>
</caw:extendComponent>
扩展maxlength
前:
扩展后, maxlength修改为15
如果扩展代码修改为:
<caw:extendComponent target="employee_code">
<caw:setProperty name="style" value="background-color: red;"
updateType="repolace"/>
<caw:setProperty name="maxlength" value="#{xxxBean.testProperty(VALUE)}"
updateType="extend"/>
</caw:extendComponent>
//xxxBean
public int testProperty(int value) {
return value + 10;
}
在这段代码中,EL表达式中,通过表达式VALUE
就获取了扩展之前的值,这样可以通过代码来扩展。修改如下:
如果在上述代码中,updateType=replace
, 则EL表达式中,不能使用VALUE
变量。
# 装饰器(DECORATOR)
# 装饰器介绍
装饰器(Decorator), 是在代码中提供给其他开发者干预、调整代码结果的一种方式。其本质上是接口类(Interface)
只有期望其他开发者来进行扩展的时候, 才需要定义装饰器。
创建一个装饰器,只有两步:
- 定义一个装饰器(就是一个Java Interface)
- 在代码中启用这个装饰器
装饰器提供两种形式:
- 事件形式。 即通知所有注册的装饰器,执行某个动作。
- 扩展值形式。即通知所有注册的装饰器,来对某个值进行调整,最终将这个调整后的值返回给主线程。
# 定义装饰器
- 只需要定义一个Interface, 这个Interface必须继承
ViewDecorator
。 - 在这个Interface中,定义你期望第三方开发者实现的API 实例:M18版本比较装饰器
public interface VersionCompareDecorator extends ViewDecorator {
public List<FieldLocator> versionCompare(SqlEntity entity, SqlEntity comparedEntity);
}
# 启用装饰器
定义好装饰器后,需要在需要扩展的代码处,启用装饰器。 启用装饰器,有两种方法,对应装饰器的两种形式:
- 事件形式。这种情况仅用于:通知所有装饰器,执行某个动作(函数),不需要返回值。
//ViewController
public <T extends ViewDecorator> void notifyDecorator(Class<T> target, DecorateAction<T> action, Object[] params)
controller.notifyDecorator(MyDecorator.class,
(decorator, params) -> decorator.myDecoratorAction(params[0], params[1]),
new Object[]{param1, param2});
target
: 装饰器的定义类。比如VersionCompareDecorator
action
: 事件接口实现类,接口定义如下public interface DecorateAction<T> { public void doAction(T decorator, Object[] params); }
params
用来传入DecorateAction
的参数
值扩展形式。
//ViewController public <T extends ViewDecorator> void visitDecorator(Class<T> target, DecorateVisitor<T> visitor, Object[] params, DecorateVisitCallback<T> callback);
controller.visitDecorator(VersionCompareDecorator.class, (decorator, params) -> decorator.versionCompare((SqlEntity) params[0], (SqlEntity) params[1]), new Object[] { compareEntity, comparedEntity }, callback);
target
: 同上,指定装饰器的接口类。visitor
: 类似“事件形式”中的action
, 是一个DecorateVisitor
实现类。用来指定调用装饰器指定的函数,并返回一个值。public interface DecorateVisitor<T> { public Object visit(T decorator, Object[] params); }
params
: 用来传入DecorateVisitor
的参数callback
:DecorateVisitCallback
的实现对象。用来对visitor
的返回值进行操作,判断是否继续调用后面的装饰器。如果callback
的返回值是false
, 则后续的装饰器不会调用。public interface DecorateVisitCallback<T> { public boolean visited(T decorator, Object returnObject); }
# 使用装饰器扩展功能
开发者使用已经定义的装饰器来扩展现有功能是,必须定义一个装饰器的实现类。
- 装饰器可以是一个单独的Java类
- 装饰器可以是一个Java Bean
- 装饰器可以使一个ViewListener
要让实现的装饰器,发挥作用,必须在视图中注册:
如果装饰器是Java Bean 或者ViewListener, 因为Bean和ViewListener已经注册,所以不需要额外的注册。
如果装饰器是一个单独的Java类,则需要注册:
页面通过
<caw:viewDecorator class="className"/>
注册页面通过
<caw:viewDecorator value="#{returnDecorator()}"
注册在ViewListener的
initialized
方法中注册controller.addViewDecorator(myDecorator);
# 组件通用功能编写
在编写新组件时,如果期望组件支持M18平台通用功能,请参阅以下说明
# 支持mess自定义
在需要支持用户自定Mess的html元素上,增加data-mess
属性,属性值,就是当前html元素上的messCode
。如下:
<label data-mess="employee">Employee</label>
当用户开启“自定义mess“功能后,就可以通过右击上述<label>
元素,进行"自定义mess"操作。
# 支持组件定位
当后台的错误消息,包含组件定位时,前端会根据组件定位信息,找到相应组件,并触发“高亮”事件。
后台通过tableName
, columnName
, rowId
生成组件定位信息: tableName.columnName.rowId
- 如果是一对一的Table栏位(主表栏位), 定位信息是
tableName.columnName
- 如果是一对多的Table栏位, 则定位信息是
tableName.columnName.rowId
- 如果定位信息指向一个Table, 则信息是
tableName
组件要支持“组件高亮”事件,则必须定义组件的“定位信息”: 在组件的最外层html元素上(比如div), 存在data-locator
属性,且其值符合上述“定位信息”规则。 当后台消息包含了这个组件定位信息时,则会触发该组件的“高亮”事件。
在JavaScript
代码中,通过以下方法,添加“高亮”事件:
$('#componentId').on('highlight', function(event, locator) {
console.log(event.highlight); //输出highlight类型,目前有message、versionCompare两种。
console.log(event.status);//输出highlight状态, “on”为高亮显示,“off”为取消高亮显示
});
# 支持用户自定义
在M18平台中, 用户自定的UI包括
- 布局自定义
- 自定义栏位,支持用户选择组件
- 视图上的组件,支持用户定义属性
布局自定义功能不需要开发者做任何处理。
如果开发者新开发的组件,要支持自定义栏位和自定义属性, 请查阅以下说明
# 组件支持自定义栏位
组件如果需要支持自定义栏位,则必须在faces.xml
中配置,注明组件支持哪种类型的栏位。
<component-config>
<faces-library ns="http://www.cloud-at-work.com">
<description>faces.lib.caw</description>
<tag name="inputText">
<udfSupport udfType="string"/>
</tag>
</faces-library>
</component-config>
如上示例, 通过<udfSupport>
来设置组件支持的“自定义栏位”类型。
自定义栏位类型有string
,text
,number
,date
,boolean
,color
,image
,combo
,lookup
。
# 组件支持用户自定义属性
组件如果需要支持用户配置属性,则必须在faces.xml
中,指明哪些属性支持用户配置。
以inputText
为例
<tag name="inputText">
...
<udfSupport udfType="string"
helper="com.multiable.web.udf.InputTextHelper" height="320">
<attribute name="mess-label" source="labelMess" editor="mess">
faces.caw.faces.label
</attribute>
<attribute name="mess-info" editor="mess">
faces.caw.faces.info
</attribute>
<attribute name="markedRequired" type="Boolean" source="colReq">
faces.caw.faces.required
</attribute>
<attribute name="readonly" type="Boolean" editable="false">
faces.caw.faces.readonly
</attribute>
<attribute name="maxlength" type="Integer" source="textLength">
faces.caw.faces.length
</attribute>
</udfSupport>
...
</tag>
组件通过attribute
来声明属性信息。 attribute
属性如下:
属性 | 说明 |
---|---|
name | 属性名称 |
type | 属性值类型。支持Byte ,Boolean ,Character ,Short ,Integer ,Long ,Float ,Double ,String |
default | 默认值 |
source | 从自定义栏位信息中的那个栏位获取值,自定义栏位可用属性见表格下方。 |
editor | 指明用来设置当前属性值的组件类型,可选有:css , mess (仅当属性值类型是String 时有效) |
visitable | 是否显示在用户配置属性界面上。 |
editable | 是否允许用户在配置属性界面上修改 |
tip | 用来提示用户当前属性的用途,messCode 类型 |
option | 如果当前属性用下拉框选择项来编辑,那么option用来指明pattern |
src | 指明一个JSF视图作为属性的配置视图 |
required | 是否必填 |
自定义栏位中可用属性code
,desc
,colType
,labelMess
,colReq
,textLength
,numPoint
,numDecimal
,searchTypeId
,stSearchType
# 组件编写额外的设定页面
组件如果要提供特定的配置页面让用户配置,可参照如下操作:
<udfSupport>
上指定配置视图文件<udfSupport udfType="string" settingUrl="/view/dialog/myConfig.xhtml"> ... </udfSupport>
传入到视图的参数
配置视图是以dialog的形式出现。传入视图的参数有
attributes
,一个Map<String, Object>
,包含当前组件的各个属性值。视图返回属性值
dialog关闭时,必须传回修改后的属性值
Map<String, Object>
。处理方式见对话框关闭和回调方法
# 将用户自定义的属性值,运用到组件上
- 如果自定义的属性名,是组件本身支持的,则不需要额外工作,就能生效
- 如果自定义的属性名,不是组件支持的,而是需要开发者进行转换的,则需要开发者定义一个
UdfHelper
。
定义UdfHelper
步骤:
定义UdfHelper类。 (必须继承于
UdfHelper
)在
<udfSupport>
上通过helper
属性设置<udfSupport udfType="string" helper="com.multiable.web.udf.InputTextHelper"> ... </udfSupport>
在
UdfHelper
中, 有两个方法必须覆写。public void applyTag(FaceletContext ctx, UIComponent component, Tag tag) { TagAttribute attribute = tag.getAttributes().getAttribute("myProp"); String attrValue = attribute.getValue(); component.getAttributes().put('style', attrValue); }
在这个方法中,所有的组件属性都在
Tag
中,通过Tag
获取组件不支持的属性(myProp
),然后处理myProp
。public void applyAttribute(FacesContext context, UIComponent component, Map<String, Object> attributes) { String attrValue = attributes.get('myProp'); component.getAttributes().put('style', attrValue); }
在这个方法中,所有组件属性都在
attributes
中。 同样处理myProp
# 特定组件/功能介绍
# 模块向导
模块向导是将一系列模块合在一起,用一个向导界面向用户展示,并引导用户做出一系列复杂动作。
每个模块想到,都具备两个元素:
- key: 唯一标识
- path: 向导视图文件
# 启动向导
Java端启动一个向导
WebUtil.startGuide("udfEditor", '/view/frame/udfEditorGuide.xhtml');
JavaScript启动一个向导
top.myApp.startGuide({ key: 'udfEditor', path: '/view/frame/udfEditorGuide.xhtml' });
# 向导文件的编写
向导视图遵循JSF视图规范。
在向导视图中,通过一个向导组件(<caw:frameWizard>
)来定制向导内容。
向导示例:
<caw:beanDeclare name="ueGuide"></caw:beanDeclare>
<h:form id="form">
<caw:frameWizard id="wizard"
style="height: 100%;"
completeValidator="#{ueGuide.validateComplete}">
<f:facet name="start">
<!--定义开始视图-->
</f:facet>
<f:facet name="end">
<!--定义开始视图-->
</f:facet>
<f:facet name="message">
<!--定义消息显示视图-->
</f:facet>
<caw:frameStep id="udfEditor" menuCode="udfEditor"
listener="#{ueGuide.getStepListener(0)}">
<!--定义步骤1说明画面-->
</caw:frameStep>
<caw:frameStep id="udfMenu" menuCode="udfMenu"
listener="#{ueGuide.getStepListener(1)}"
validator="#{ueGuide.validateStep}">
<!--定义步骤2说明画面-->
</caw:frameStep>
<caw:frameStep id="moduleFuncSetting" menuCode="moduleFuncSetting"
listener="#{ueGuide.getStepListener(2)}"
validator="#{ueGuide.validateStep}">
<!--定义步骤2说明画面-->
</caw:frameStep>
</caw:frameWizard>
</h:form>
<caw:frameWizard>
属性说明
属性 | 说明 |
---|---|
completeValidator | 指定一个EL表达式,用来判断用户是否完成向导所有指定步骤。 |
onChange | 指定向导的变更事件(步骤变化时触发) |
onComplete | 指定向导完成时响应的函数 |
<caw:frameWizard completeValidator="xxBean.validateComplete"
onChange="xxBean.onWizardChange"
onComplete="xxBean.onWizardComplete">
...
</caw:frameWizard>
//xxBean.java
public void validateComplete(FacesContext context, UIComponent component, FrameWizardCompleteEvent event) {
if(...) {
event.setValid(false); //表明当前向导没有完成。
}
}
public void onWizardChange(FrameWizardChangeEvent event) {
}
public void onWizardComplete(FrameWizardCompleteEvent event) {
}
**<caw:frameStep
**用来设定向导的每一个步骤
属性 | 说明 |
---|---|
menuCode | 指定这一步骤的Frame视图 |
listener | 为这一步骤的Frame视图添加ViewListener |
params | 为当前步骤的Frame视图传入的参数,接受Map<String, String |
validator | 为当前步骤增加一个校验方法:判断是否可以进入这一步骤。 如果event.setValid(false) , 则表示当前不能进入这一步骤 |
validator示例
<caw:frameStep id="step1" validator="xxBean.validateStep"></caw:frameStep>
//xxBean
public void validateStep(FacesContext context, UIComponent component, FrameWizardChagneEvent event) {
String step = component.getId();
if("step1".equals(step)) {
event.setValid(false);
}
}
定制facets
可以通过facets
标签为frameWizard
定制一些辅助视图:
facets 名称 | 说明 |
---|---|
start | 为向导指定一个起始视图 |
end | 为向导指定一个结束视图 |
message | 为向导制定一个消息视图 |
# 进度显示
# 概述
当我们需要为一个后台动作,提供友好的进度显示时,可以参考M18提供的进度显示方案。
# 启动和销毁
启动和销毁只能在前端JavaScript中处理。
# 创建一个具备默认视图的进度条
var progressBar = myView.createProgressbar('batchDelete', {
colorStyle: 'primary',
interruptable: false,
progress : {startValue : 5,
duration: 90,
description: 'progress.batchDelete'}
});
这里的配置项说明如下:
属性 | 说明 |
---|---|
colorStyle | 定义颜色样式。可选primary ,warning ,info ,danger ,success。 |
interruptable | 是否可以中断, 默认false 。 |
showBar | 是否显示进度条,默认true 。 |
showProgressText | 是否显示百分比,默认true。 |
showDescription | 是否显示进度说明,默认true。 |
progress | 后台进度的配置。 startValue 指任务到达后台时,主任务的进度值;duration 后台任务占主任务的进度范围;description 是主任务的描述 |
# 创建一个自定义视图的进度条
//定义一个进度视图, 并将其放到页面上
var myProgressUI = $('<div></div>').appendTo(document.body);
//创建一个进度对象
var progress = myView.createProgress('myProgressBar', {});
//监听事件ready/update/end/destroy, 更新自定义的进度视图
progress.on('update', function(data){
myProgressUI.text(data.progress + '%');
});
# 启动进度条
//默认进度条
progressBar.start(function() {
//action here
});
//自定义视图的进度条
progress.start(function() {});
启动进度条的同时,指定一个动作。
# 销毁进度条
//默认进度条销毁
progressBar.destory();
//自定义视图的进度条销毁
progress.destory();
myProgressUI.remove();
# 进度状态的更新
前端JavaScript更新
var data = {
progress: 50,
progressDescription: 'description for current action',
description: ''
};
//Progress对象
progress.update(data);
//ProgressBar对象(默认进度条视图)
progressBar.update(data);
后台Java更新
//ProgressUtil.java
public static void updateProcess(int progressValue, String progressDescription);
public static ProcessStatus updateProcessStatus(int progressValue, String ProgressDescription);
参数progressValue
指当前动作的进度百分比
参数progressDescription
指对当前动作的描述
updateProcess
和updateProcessStatus
区别:
updateProcess
会检查当前任务是否已经被中断, 如果中断,会抛出异常。
updateProcessStatus
不会抛出异常,会访问当前任务的状态。 如果当前任务已中断,不会更新UI。
任务的状态有:
public enum ProcessStatus {
NULL, INITIALIZED, RUNNING, COMPLETED, INTERRUPTED
}
# 进度常用API
检查是否中断
ProgressUtil.checkInterruptStatus(); //如果中断,抛出异常 ProgressUtil.isInterrupted(); //返回是否中断,不会抛出异常
开启新的进度子任务
//参数: String key, String description, int startValue, int duration ProgressUtil.startProgressProcess("key", "description", 40, 20); ProgressUtil.startGroupProcess("key", "description", 40, 20)
在当前任务重,开始一段任务作为子任务。
key
作为子任务的标识。description
子任务的描述。startValue
子任务开始时,当前任务的进度百分比。duration
子任务在当前任务所在的百分比。开启新的进度子任务(包装)
//参数: String key, String description, int startValue, int duration ProgressUtil.startWrappedProcess("key", "description", 40, 20);
参数说明与上面一致。
这个方法主要用于:在当前任务中,调用另一个函数(函数内部有独立进度信息)。
示例:
public void mainTask() { ProgressUtil.startGroupProcess("mainTask", "Main Task"); ... ProgressUtil.startWrappedProcess("wrapTask", "Wrapped Task", 40, 20); wrapTask(); ProgressUtil.endProcess(): ... ProgressUtil.endProcess(); } public void wrapTask() { ProgressUtil.startProgressProcess("wrapTask", "Wrapped Task"); ... ProgressUtil.updateProgress(50); //当执行到此时,mainTask进度是(40+20*50%) ... ProgressUtil.endProcess(); }
为独立的任务添加进度信息
//参数: String key, String description ProgressUtil.startProgressProcess("key", "Description"); ProgressUtil.startGroupProcess("key", "Description")
结束进度任务
ProgressUtil.endProcess();
# 多语言支持
M18平台支持多语言。默认提供简体中文、繁体中文、英文三种语言。用户可以自定义其他语言。
开发者通过M18平台提供的语言条目(mess)修改工具进行增加/修改/删除。每一个语言条目,都有一个messCode
,开发者在代码中通过messCode
来获取对应的文本内容。所有的语言条目都可以直接从后台获取。但是在前端JavaScript中只能获取部分,只有在新增语言条目时,设定了webmess=true
,才能从JavaScript获取。
- Java端通过
CawGlobal.getMess("messCode")
来获取文本 - JavaScript通过
myView.getMess('messCode')
来获取文本。
语言切换事件
在用户切换语言时,开发者可以在代码中,得到事件通知,以便开发者根据语言变动做出对应的调整。
JavaScript监听语言切换事件
$(document).on(JsView.EVENT.BEFORE_LANGCHANGE, function(event) { //Do something before language changed }) $(document).on(JsView.EVENT.AFTER_LANGCHANGE, function(event) { //Do something after language changed })
Java中监听语言切换事件
在任意ViewListener中,通过
actionPerformed
监听public void actionPerformed(ViewActionEvent vae) { if(WebUtil.langChangeCommand.equals(vae.getActionCommand())) { //do something } }
# 常用API
# JSF页面常用配置
# 可用配置项
<caw:view>
配置
设置
view
的readonly
和disabled
属性<caw:view readonly="#{expression}" disabled="#{expression}"> ... </caw:view>
用于设定统一设定当前
view
中所有的输入性组件的readonly
和disabled
属性。onConstruct
属性<caw:view onConstruct="#{method}"> ... </caw:view>
用于指定在
ViewController
创建后,执行的方法onDestroy
属性<caw:view onDestroy="#{method}"> ... </caw:view>
指定在
view
销毁前执行的方法。
为ajax请求增加固定参数
在View
中,每次ajax请求都是基于action
url来发出的。如果期望每次ajax请求都能够附加一些信息,可以如下操作:
<caw:view>
<caw:actionPraram name="paramName" value="paramValue"/>
...
</caw:view>
这样在每次ajax请求中,都可以在requestParam
中获取paramName
的值paramValue
。
# 可用函数(Function)
获取语言文本:
#{caw:mess('messCode')}
<label>#{caw:mess('messCode')}</label>
获取组件
clientId
:#{caw:webID('id')}
<caw:inputText id="textId"></caw:inputText> <caw:commandButton update="#{caw:webID('textId')}"></caw:commandButton>
获取系统资源的URL:
#{caw:resourceUrl('name', 'library')}
# Java常用前端API
依据组件
id
更新组件WebUtil.update("id");
改变组件状态
WebUtil.setDisabled(false, "component1Id", "component2Id"); //更新disable状态 WebUtil.setRendered(false, "component1Id", "component2Id"); //更新Render状态 WebUtil.setVisible(false, "component1Id", "component2Id"); //更新visible状态
注意:
- 只有当组件支持对应状态显示时,才能生效。
- 上述代码只是调整组件状态,要使界面呈现,比如更新相关组件。调用
WebUitl.update("id")
方法。 - 对于Render状态,修改组件后,必须更新组件的父组件才能生效。
render
,visible
状态, 如果开发者在JSF页面(xhtml文件)上,声明组件时,指定的render
,visible
状态为false。 则java端不能通过上诉代码调整状态。disable
状态,如果开发者在JSF页面(xhtml文件)上,声明组件时,指定的disable
状态为true。 则java端不能通过上诉代码调整状态。
设置焦点
WebUtil.focusComponent("componentId");
注意:
不能保证100%生效,因为焦点的切换受到多重原因的影响。
多使用在页面初始化。
在ajax响应代码中,向ajax oncomplete方法传递参数
FacesUtil.getAssistant().addCallbackParam("callback", 5);
ajax.oncomplete = function(xhr, status, args) { if(args.callback > 4) { //xxxxx } }
查找组件
WebUtil.findComponent('componentId'); //根据id查找组件
一些工具类
WebUtil.java
前端通用方法MessageUtil.java
前端消息通用方法SysAlertUtil.java
是系统提醒通用方法
# Javascript常用API
JavaScript触发视图的ViewListener事件
myView.triggerAction('actionCommand');
会触发ViewListener的
actionPerformed
方法。JavaScript触发Module的事件(Save/Create/Read...)
myFrame.triggerAction('read', {id: 32}); myFrame.triggerAction('save');
JavaScript更新指定组件
myView.ajaxUpdate('componentId');
前端获取mess:
myView.getMess('messCode'); //仅仅对放置在前端的messCode
JavaScript常用方法定义在
cawView.js
中