# 前端开发文档

M18平台页面开发框架基于JSF。 JSF的一个基本概念是组件。页面通过一个组件树来渲染。每个UI组件通过EL表达式获取值,并将其显示在页面上;并在数据提交时,将页面上用户修改后的值,同步到服务器端;也可将用户的界面操作,转化成事件,提交到服务器,通过EL表达式响应。

# JSF页面与后台交互框架

# 界面交互概念

# JSF交互概念

JSF与后台的交互,不论是值的绑定,还是事件的响应,都是通过EL表达式来实现的。页面上的JSF组件通过EL表达式,与后台的Java Bean对象绑定,进而完成值的获取、提交,组件上的事件响应。JSF页面,与后台Java Bean的关系如下图。

jsfelbean

# M18交互

M18中,保留JSF所有交互方式,并支持另一种交互方式。交互关系图如下:

m18listener

在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的点击事件,inputTextfocusblurchange事件。
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有如下几种方式

  1. 简单的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>
    
  2. 对于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>
  1. 在JSF页面,通过JAVA类名指定一个ViewListener对象

    <caw:viewListener class="com.multiable.bean.view.ModuleViewListenerTest"/>
    
  2. 在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继承于viewBeanModuleBean, 和其他继承于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层级

controllers

注意

  • 每一个视图(View)都有一个视图控制器,但是视图控制器的实例对象可能是上述的某一种视图控制器对象。
  • 任意一个ViewListener对象都可以添加到任意一个视图。但是不同的ViewListener接口,存在其特殊的接口方法, 而这些接口方法仅仅在特定的视图控制器中才有效。
  • 视图(View)的分类,以及其对应的视图控制器,请参阅M18 UI概念

# M18 UI概念

M18平台运行在一个浏览器窗口中,M18平台中的文档、模块以标签页的形式显示在M18浏览器窗口中。

  • M18浏览器窗口视图,称为“App视图”
  • 在“App视图”中,以标签页显示的视图称为“Frame视图”
  • 在“App视图”中,弹出的窗口,称之为“对话框(Dialog)视图”
  • 在“App视图”中,用来显示资料信息的悬浮窗口,称之为“资料名片(Namecard)视图”
  • 所有视图,都有独立的视图文件(JSF文件,xhtml格式)

App视图

homeui

Frame视图

frameui

对话框(Dialog)视图

dialogui

资料名片(Namecard)视图

namecardui

模块(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下。这样的布局组件有fieldGroupcontainer

# 组件编写规则与要求

  1. ID必须填写,并且唯一

    • 我们的页面是允许第三方开发者进行扩展,第三方开发者是通过组件ID来进行定位和扩展的。
    • 我们的页面是允许用户进行自定义页面布局,自定义页面时,组件的区分依据就是组件ID
    • 在代码中,进行组件的获取或者指定更新,我们是需要知道组件的clientId(根据ID自动生成),而这个不固定的clientId会严重加大维护成本,因此在M18中,我们提供了进行“组建获取和指定”的替代方案:根据固定不变的组件ID来进行组件的获取和指定。
  2. 页面布局使用fluidPanel,页面可以分为多个区域,每个区域使用fluidPanel布局。详细见布局组件介绍

  3. module中,页面组件通过指定tableNamecolumnName映射后台数据。并能够自定生成组件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(上述例子中,组件idxxTable_yyColumn)

# 页面资源引入

在所有视图文件中,只需要引入当前视图所需要的特定css/js资源。系统通用的资源,会自动加载。

自动加载的系统资源有:jquery.jstether.jsbootstrap.jscawView.jsbootstrap.csscawStyle.csscawTheme.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的补充,显示文档数据。

    提供默认实现,支持开发者定制

moduletemplate

如何使用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所有的事件,同时在以下动作触发时,提供BEFOREAFTER两种事件: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.frameDialogTarget.app
setType(DialogType type) 指定Dialog类型:DialogType.message,DialogType.htmlDialogType.custom
setContent(String content) 当类型是DialogTarget.messageDialogTarget.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配置项的方法有如下三种:

  1. 通过JavaScript方法显示Dialog时,通过myView.showDialog(dialogOption)设置参数。

  2. 通过Java方法显示Dialog时,通过自定义CawDialog来进行配置项设置

  3. 在Dialog的独立视图文件中,通过JSF标签设置,如下

    <caw:dialogOption width="500" height="600" title="myTitle" draggable="true" resizable="false" maximize="false" maxView="false"></caw:dialogOption>
    

# 对话框关闭和回调方法

# Dialog的关闭

Dialog在关闭时,带有两个信息:

  1. 关闭状态DialogStatus, 标记Dialog关闭状态,目前有如下状态:OK,CANCEL,YES,NO
  2. 关闭时返回的数据returnObject, 是Dialog传回给callback的数据

关闭Dialog的方法

JavaScript方法:

myView.closeDialog(dialogId, status, returnObject);

Java方法:

仅仅在当前DialogView视图中,才能关闭当前Dialog。

//controller是DialogController对象
controller.closeDialog(DialogStatus status, Object returnObject);

# 回调方法

Dialog回调方法,指的是:当Dialog关闭后,系统根据Dialog关闭状态和传回的数据,执行的特定的方法。

添加回调方法有四种方式:

  1. 在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
             }
    	}
    }
    

  2. 在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
    }
    
  3. 在弹出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);
    
  4. 通过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对消息的显示处理,我们将消息分为以下三类:

  1. 原生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'));
    
  2. 通知(Notice)类消息

    通知类消息在每个页面(视图)都有默认显示。开发者不需要指定显示组件。

    如果开发者指定了显示组件, 并且渲染了通知消息,则这个消息不会在经过系统默认显示。

    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)
    
  3. 消息中心消息

    消息中心是为Frame视图定义的一种消息显示机制。消息中心用来显示一些具有附加信息的消息,这类消息可以提供一个定制的画面来显示详细的消息内容,并能够与用户交互。

    消息中心主要用来显示后台运行过程中产生的CheckMsg对象,CheckMsg一般代表服务器的一种错误信息。

    消息中心显示的消息对象是ActionMessage, 提供了两种交互方式:

    • 根据ActionMessage对象提供的定位信息,高亮显示对应界面元素。

      ActionMessage message = ...;
      FieldLocator locator = ...;
      message.addLocator(locator);
      
    • 根据ActionMessage对象提供的messageKeymessageData,提供详细界面来显示详细的信息。

      ActionMessage message = ...;
      message.setMessageKey('core_101');
      message.setMessageData(object);
      

    通过CheckMsg对象生成ActionMessage时,会自动根据CheckMsg生成定位信息、messageKey和messageData。

    消息中心的消息显示:

    msgCenter

    通过下列方法添加消息中心消息:

    //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定义消息中心视图

  1. 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

    • keymessageKey

    • render非必填。如果填写,则指定一个class。 这个class必须实现了MessageRender接口,用来为message生成显示的message/detail内容

    • to-view-id指定详细视图的JSF文件

    • view-param指定传给这个视图的参数

  2. 编写详细视图页面

    消息详细视图就是一个普通的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提供的消息显示组件

messagesdynamicMessages

# 资料名片(Namecard)开发

资料名片是一种悬浮卡片窗口,用来显示文档的数据。多用于lookup栏位。

namecard

# 资料名片触发规则

资料名片在鼠标悬停在HTML元素上时,触发显示。

HTML元素符合以下条件,就能够触发资料名片:

  1. 元素具备属性data-lookup,指明lookupType(定义在stSearch.xml中)

    <input id="text1" type="text" data-lookup="employee"/>
    
  2. 元素能够获取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时,以默认模板为基础,增加fieldlink;为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来配置资料名片。见配置默认数据和模板

用户通过【自定义查询】下的资料名片来配置资料名片。见用户自定义资料名片

# 用户自定义资料名片

udfnc

从上图可知,图片设置数据栏位设置附加链接设置模板设置都和开发者配置一一对应。这里的模板属性设置界面,是由模板开发者提供的。

# 开发资料名片模板

资料名片模板只负责资料名片数据的显示,可以存在配置项供用户/开发者进行配置。如果提供配置项供用户配置,则必须提供配置界面。

开发资料名片模板只需要提供模板页面,模板配置,并在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} 在注册时,settingoption属性指明的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对象,请编写JSONserializerJSONDeserializer

页面编写说明

在编写配置视图时,视图内容必须包含在<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平台上有三种组件支持快捷键:commandButtontoolbar(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. 如果没有填写,系统会取commandButtonid, toolbar(actionItem)id+actiondropmenu(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)的开发

# 系统提醒功能相关视图

  • 系统工具栏, 系统提醒入口

alert

  • 系统通知列表

    alertlist

  • 系统提醒弹出通知窗口(系统右下方)

    alertpop

  • 系统提醒详细视图

    alertview

# 系统提醒常用接口

系统提醒常用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的基本变量:idtype等(定义在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格式)来描述系统提醒的具体内容。

# 要点说明

  1. 在提醒列表视图(弹窗)中,增加按钮(或其他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"时,用户点击按钮后,会自动关闭提醒列表视图,否则不会关闭。
  1. AbstractAlertRender的使用

为了简化开发者的开发,M18提供了一个渲染器的基类AbstractAlertRender。 开发者可以选择使用它,也可以选择重新编写渲染器。

AbstractAlertRender提供的列表视图布局如下图

alertui1

​ 包含四个部分:图标区、标题区、内容区、时间戳。

  • 图标区,通过覆写方法getIcon来定制
protected ImageObject getIcon(SysAlertItem item) {
  return null;
}
  • 标题区,可以通过以下三种方式来定制
    • 通过定义系统提醒Java对象中的方法getMessageText()来指定标题内容。不支持html格式
    • 通过定义渲染器中的方法encodeAlertTitle来指定标题内容。支持html格式
    • 通过覆写AbstractAlertRender的方法encodeTitle来修改标题区渲染。
  • 内容区,可以通过以下三种方式来定制
    • 通过定义系统提醒Java对象中的方法getDetailText()来指定内容区的文本。 不支持html格式、
    • 通过定义渲染器中的方法encodeAlertContent来指定内容区。 支持html格式。
    • 通过覆写AbstractAlertRender的方法encodeItemContent来修改内容区渲染
  • 时间戳,不支持开发者扩展。
  1. 系统提醒详细视图的编写

    开发者可以通过实现渲染器的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>

注意:

  1. 所有的配置必须在sysalert-config之间。

  2. 注册渲染器<rendererType name="code">className</rendererType>

    这其中的code必须是系统唯一的。

    这里的className必须是一个渲染器(SysAlertRender)的实现

  3. 注册Java对象<sysalert name="code"><object>className</object></sysalert>

    这其中的code也必须是系统唯一的

    className必须是一个SysAlertObj

  4. <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用来指定已经注册的渲染器。

# 系统链接规则

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配置的每个menucode

    resourcePath: 模块的路径。 以employee为例,未见路径为view/module/employee.xhtml,所以其resourcePathview/module/employee

    randomKey由系统生成

  • 参数(?id=63))

# 获取系统链接

获取指定Frame的系统链接,可通过以下API获取

WebUtil.getFrameLink(String menuCode, Map<String, String> params);

# 链接参数的解析

一个Frame视图如果要支持参数,则必须在视图java代码中对参数进行获取和解析。一般情况下,我们在ViewListener的initalized接口中处理。

# 推送机制的使用

推送(PUSH)是服务器主动向前端发送消息的一种方法。 M18平台封装了推送机制,并提供了一套友好的API。 开发者根据功能需求使用。

推送,涉及到前端后台的交互,整个过程如下:

  1. 前端发起一个到后端的连接
  2. 后端通过一个服务类(Java)来监听这个连接,并处理传送的消息。
  3. 前端或者后台关闭这个连接。

# 前端

在JavaScript代码中, 开发者可以通过myView.createWebSocket(name, option, params)创建一个websocket连接。

  • name: 与后台服务的定义对应。

  • option: 一个JavaScript对象, 支持onmessageonerroronclose, 值都是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
  • 第三个参数filterkeyPredicate都是用来指定将消息传给哪些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.toJSONStringJSON.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平台主题分为“系统主题”和“用户主题”。

  • 系统主题,只允许开发者增加。用户不可修改。

  • 用户主题,每个用户可以有一个用户主题,用户可以以当前主题为模板,调整主题的色板,从而生成自己的个性化主题。

    theme

# 用户自定义

用户自定义主题,就是用户定义“主题色板”的一个过程。

系统提供了一套”主题色板“,包含一系列颜色变量,用户可以给每一个颜色变量分配一个颜色。

这一系列颜色变量包含

  • M18系统基础颜色变量

    系统基础颜色变量包含以下五种:

    PrimaryInfoWarningDangerSuccess

    系统也提供了额外的颜色变量,用来控制组件的主题色。比如系统文本颜色必填栏位标签颜色等。

  • 特殊页面颜色变量

    M18平台为系统主页提供了颜色变量,让用户来调整主页主题色彩。

  • 开发者提供特定页面的颜色变量

    M18平台允许开发者为特定页面编写颜色变量。类似系统特殊页面颜色变量。

themeui

# 主题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%, a0~100之间

    +: 使颜色加深,后面接a%, a0~100之间

    *: 是颜色变透明,后面接一个float数值

  • 使用变量

    在css文件中,使用变量{变量名}

# 主题CSS文件可使用的变量

  • 系统基础颜色变量包含以下八种:

primaryinfowarningdangersuccessgraylightGraydarkGray

以上每种颜色变量,都具备两种渐变色变量,以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的其中一个: ViewControllerWrapperDialogControllerWrapperFrameControllerWrapperModuleControllerWrapper
  • <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 对原组件属性的修改方式:extendreplace

对于style,styleClass属性

以上述代码为例,组件employee_codestyle属性会同时具备两种设定。显示效果为:

extprop

如果将扩展代码修改为

<caw:extendComponent target="employee_code">
  <caw:setProperty name="style" value="background-color: red;" 
                   updateType="repolace"/>
</caw:extendComponent>

则组件employee_code在原视图文件中的设定会被清除。效果为:

extprop1

对于其他属性而言, 如果扩展属性的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前:

extprop2

扩展后, maxlength修改为15

extprop3

如果扩展代码修改为:

<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就获取了扩展之前的值,这样可以通过代码来扩展。修改如下:

extprop4

如果在上述代码中,updateType=replace, 则EL表达式中,不能使用VALUE变量。

extprop5

# 装饰器(DECORATOR)

# 装饰器介绍

装饰器(Decorator), 是在代码中提供给其他开发者干预、调整代码结果的一种方式。其本质上是接口类(Interface)

只有期望其他开发者来进行扩展的时候, 才需要定义装饰器。

创建一个装饰器,只有两步:

  1. 定义一个装饰器(就是一个Java Interface)
  2. 在代码中启用这个装饰器

装饰器提供两种形式:

  • 事件形式。 即通知所有注册的装饰器,执行某个动作。
  • 扩展值形式。即通知所有注册的装饰器,来对某个值进行调整,最终将这个调整后的值返回给主线程。

# 定义装饰器

  • 只需要定义一个Interface, 这个Interface必须继承ViewDecorator
  • 在这个Interface中,定义你期望第三方开发者实现的API 实例:M18版本比较装饰器
public interface VersionCompareDecorator extends ViewDecorator {
	public List<FieldLocator> versionCompare(SqlEntity entity, SqlEntity comparedEntity);
}

# 启用装饰器

定义好装饰器后,需要在需要扩展的代码处,启用装饰器。 启用装饰器,有两种方法,对应装饰器的两种形式:

  1. 事件形式。这种情况仅用于:通知所有装饰器,执行某个动作(函数),不需要返回值。
//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的参数

  1. 值扩展形式。

    //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类,则需要注册:

    1. 页面通过<caw:viewDecorator class="className"/>注册

    2. 页面通过<caw:viewDecorator value="#{returnDecorator()}"注册

    3. 在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

# 组件编写额外的设定页面

组件如果要提供特定的配置页面让用户配置,可参照如下操作:

  1. <udfSupport>上指定配置视图文件

    <udfSupport udfType="string" settingUrl="/view/dialog/myConfig.xhtml">
      ...
    </udfSupport>
    
  2. 传入到视图的参数

    配置视图是以dialog的形式出现。传入视图的参数有attributes,一个Map<String, Object>,包含当前组件的各个属性值。

  3. 视图返回属性值

    dialog关闭时,必须传回修改后的属性值Map<String, Object>。处理方式见对话框关闭和回调方法

# 将用户自定义的属性值,运用到组件上

  • 如果自定义的属性名,是组件本身支持的,则不需要额外工作,就能生效
  • 如果自定义的属性名,不是组件支持的,而是需要开发者进行转换的,则需要开发者定义一个UdfHelper

定义UdfHelper步骤:

  1. 定义UdfHelper类。 (必须继承于UdfHelper

  2. <udfSupport>上通过helper属性设置

    <udfSupport udfType="string"
                helper="com.multiable.web.udf.InputTextHelper">
      ...
    </udfSupport>
    
  3. 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'}
  });

progressbar

这里的配置项说明如下:

属性 说明
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指对当前动作的描述


updateProcessupdateProcessStatus区别:

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>配置

  • 设置viewreadonlydisabled属性

    <caw:view readonly="#{expression}" disabled="#{expression}">
     ...
    </caw:view>
    

    用于设定统一设定当前view中所有的输入性组件的readonlydisabled属性。

  • 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