1、Web开发框架(LFW)红皮书 第一章 简介 1. 产品概述 NC Web Frame是基于NC UAP基础技术和服务之上的一套WEB单据快速开发框架.依托NC UAP的底层技术,它可以轻松实现集群,以及部署于NC支持的各种中间件上。在客户端,它支持用户的IE6 +,IE7以及Firefox等主流浏览器。NC Web Frame开发框架以下简称LFW(LightWeight Framework). LFW单据运行时从组成结构上可分为基础javascript组件,javascript前台MVC结构,javascript模式化部分,后台控制逻辑及后台业务等.它的运行时结构图如下:
2、 从层次结构上由基础应用逻辑和模式化逻辑二部分组成: 基础应用逻辑:是开发一张单据的基础,它提供了全部的组件及大量可调用及扩展接口,通过它可以开发出符合需要的单据。要求对这套框架有相当程度的了解。 模式化应用逻辑:是针对常见单据应用,在基础应用逻辑之上总结出来的一套公共业务逻辑和界面。它参考NC模式化开发经验和代码,进行适合Web方面的裁减或者增强,提供多套界面UI基类.达到快速高效开发常用类型单据的目的. NC Web模式化开发综合利用了单据模版,查询模版,打印模板,报表模板以及元数据,以达到NC单据开发的统一性,稳定性和可维护性.并和NC流程平台紧密结合. 2.
3、 技术要求: 熟悉JAVA编程 熟悉NC模版体系 掌握NC元数据概念和操作 复杂单据需理解Javascript语法 第二章 基本概念和架构 1. LFW单据组成: PageModel + Jsp页面 + 单据配置类(或者单据配置文件).它的运行时数据是保存在每个客户端的。并使用Ajax动态请求换加载数据。 PageMeta:可以理解为页面UI元数据。它包含页面中所有组件,数据结构及事件定义。它一般被PageModel所包含并根据用户权限进行”剪裁”。在LFW的设计中,PageMeta的来源是可定制的,可以来自配置文件,可以来自于单据模板,也可以使用代码”create”出来。可
4、以根据不同的环境进行选择。Pagemeta一般是可缓存的部分。 PageModel:PageModel是运行时由页面驱动的一个类,它不但包含了UI元素的描述(Pagemeta),也包含了运行时的权限信息,以及实际数据。它一般是运行时进行初始化数据和逻辑脚本生成的地方,由于包含大量运行时数据,不能被缓存。且一次性的转换成客户端数据和脚本之后生命周期就已经结束。 Jsp页面:Jsp页面在LFW单据中较多的应用为一个模板文件,它将展现与数据相分离。通过LFW提供的布局和控件组合,可以很轻松的进行单据的外观改变,而不需要进行后台业务逻辑的改变。 页面组件和Tag:LFW单据提供了丰富
5、的组件和封装Tag,其中包含了容器型组件和一般组件。容器型组件主要用来进行布局,一部分参与逻辑(比如Tab组件)。一般组件主要用来进行数据绑定(比如Grid,Tree,Form等)以及进行事件处理(比如Button,Image等)。详细使用方法可以参照LFW的API文档。组件和下面提到的Dataset都包含客户端和服务端两个版本。服务端的组件主要是组件属性的定制,而客户端才是真正的实现。 Dataset:Dataset客户端实现.客户端Dataset是服务器端Dataset的一个对应体,也是前台MVC结构中数据事件的主要驱动者。一个数据集结构往往对应数据库中一个表结构或者一个值对象(Va
6、lueObject)结构。任何对于Dataset的改动将形成具体的Dataset事件,通知对它进行绑定的控件以及相应的页面通用逻辑,通过公共逻辑和自定义脚本,可以完成对应UI的改变和特殊逻辑的处理。 由于Dataset间往往存在主子关系。子Dataset中的各部分数据分别对应主Dataset的一条数据,因此,为了性能考虑和操作的便利性,对Dataset的实际数据的存储方式,并非采用二维平面结构,而是按照 (主数据一行的值---子数据一个块)的Map方式存储的。由于Dataset在某些页面中处于“主“地位, 而在另一些页面中可能处于“子“地位,统一起见,所有Dataset的存储方式都遵从
7、上述规则, 对于主Dataset,它本身的父数据key的值默认使用Dataset.MASTER_KEY,即主Dataset是 只有一个数据块的特殊Dataset。 下图为主子Dataset存储结构图: 从上图可见,Dataset的具体数据是存在于块数组中的,即DsContent中。DsContent又根据页数分割为多个页块。通过这种结构,可以很方便的实现分页,数据缓加载,整个页面替换等操作。 2.“模式化“: Lfw提供的模式化概念衍生自UAP平台。从我们的经验来说,对一个个人项目,提供一个灵活可扩展的应用框架,可能已经足够。但是对于一个需要多人协作,并长期维护升级的产品
8、来说,一个统一外观,统一的行为模式,及统一的开发方式的框架是不可或缺的,它不但能通过统一业务逻辑的处理,避免重复制造轮子,而且对于尽可能避免Bug以及降低维护成本,有着很大的意义。 Lfw框架在UAP 模式化多年经验总结的基础上,提供了一套适合Web单据开发的UI模式化框架。它极大减少了UI开发的工作量和重复程度,并在各个方面提供了扩充方式,使得模式化下开发“不规范“的界面也很简单。 2.1基本结构 如果熟悉NC UAP现有模式化开发概念和结构,那么理解NC Web模式化开发便不困难.NC Web模式化开发,借用了NC UI模式化的很多概念和操作逻辑,以快速获得逻辑的严密性和操作的
9、统一性.同时,由于Web开发毕竟不同于Swing开发,它也针对这种不同进行了适当的优化. a.客户端组成: 各种UI直接继承自AbstractBillUI.目前提供CardUI,ManageUI及ListUI的默认实现.由于javascript语言的动态性,以上三种UI,可直接适用于树型UI,无需重新派生新的UI. Dataset:它直接担任了前台事件的驱动者和数据缓存功能.任何针对Dataset的改变,都将形成特定事件,发送到绑定它的组件,比如DataChangeEvent。建议所有不依赖UI的数据操作,直接操作Dataset.这样通过Dataset内置功能和公共逻辑,可以很轻
10、松的获得页面各显示界面的同步.Dataset内建缓加载机制,通过DatasetRelation定义的主子Dataset,可在主Dataset选中行状态变化时,自动调用后台逻辑缓加载对应的子数据。 ButtonManager:此js版按钮管理器,负责创建各菜单按钮并根据当前的操作状态和业务状态进行按钮可用状态控制.它的使用,使得现有NC按钮及可轻松获得重用。并可进行扩展定义,以适应更复杂的状态控制。 . BillController. 此Controller对应前后台版本.均用来配置单据信息.前台版本Controller将运行时自动生成,无需干预. BillEventHandle
11、r. 按钮事件处理类.此EventHandler默认对NC绝大多数预置按钮进行了业务逻辑实现. b.服务端组成: 服务端进行业务逻辑相应的IRequestProcessor的实现。默认的,LFW框架提供了基于Dataset的DefaultDatasetBasedProcessor LFW提供的几种常见单据类型模版: LFW借鉴NC UAP模式化的经验,根据实际使用情况,将单据分为以下三种基本类型,并对每种类型进行细分,分别提供了模版和公共逻辑: a) 卡片式单据 卡片式单据适合依次编辑单条主数据和对应子数据的操作模式.根据具体的业务模型,分为: ü 单表头 ü 多表头(
12、对应原来的单表体) ü 主子表 b) 列表型单据 列表型单据提供了基于Grid的行编辑模式。 ü 主子表 ü 多表头 c) 管理型单据 管理型单据具有列表型单据的浏览优势,又有卡片单据的编辑方面的优势。 ü 主子表 ü 多表头 以上单据均可对接流程平台。 在以下各章节中,将根据LFW提供的demo,详细介绍每种单据的开发方式. 第三章 搭建环境 1. 建立Module project 首先保证本地NC_HOME路径配置正确。 2. 创建WebContext Web context 是web应用的一个逻辑路径。每个产品
13、对应一个context。形如 http://localhost/portal 的应用,这个portal即为对应的context。UAP平台默认提供LFW的工程,此工程是所有基于LFW框架的web应用所必需的。它提供统一的脚本、stylesheet等资源下载,并提供公共模板、公共配置资源和公共安全认证机制。 成功生成Module project后,使用如下插件转换成LFW工程,右键点击Project,在弹出菜单中,选择MDE Tools -> CreateWebContext: 默认选中LFW工程, 这样,我们就建立了一个context叫做demo的web
14、应用。 3. 目录结构 当完成以上步骤后,工程目录如下所示: 4. 快速运行 在Run as 中选择LFW Web Client 启动LFW应用,如下: 当server启动后,会弹出IE页面,并显示Lfw 的快速启动页面,如下图: 如果您看到上图,恭喜您,您的LFW配置成功! 第四章 如何制作卡片型单据 卡片型单据是最基本的一类单据,卡片型单据可分为单表头,多表头,主子表三种变体类型。下面我们首先开发标准卡片型单据。 1. 卡片型单据页面示例 a) 单表头的卡片型单据页面 b) 多表头的卡片型单据页面
15、 c) 主子表的卡片型单据页面 2 卡片型单据的快速开发 这里介绍的是一般通用的主子表型的卡片单据的开发过程。 2.1 前期准备 2.1.1开发元数据模型 我们将要开发的模型是一个公司、下属部门的单据,其中公司和部门中之间是聚合的一对多的关系,而且都有对员工的引用,部门分等级。根据以上需求,我们可以建立模型了,如图2.2.1.1所示,首先拖入公司和下属部门的实体,员工的引用,还有部门级别的枚举。然后从数据库中导入公司实体的字段属性,如图2.2.1.2和2.2.1.3,导入属性后需要配置实体的属性,如图2.2.1.4,这其中要配置此实体的访问类型为AggVO,关键
16、属性为主键pk_branch,类路径和类名需要指定,最后还要配置此实体在库中对应的表名。 图2.2.1.1 图2.2.1.2 图2.2.1.3 图2.2.1.4 此模型的其他实体配置可参照上面的步骤建立,配置好的模型如图2.2.1.5所示。模型建立好以后就可以导出java源文件了,如图2.2.1.6,生成的类就是上述在“类名称”中配置的,导出的java源文件如图2.2.1.7如图示。最后发布元
17、数据。 图2.2.1.5 图2.2.1.6 图2.2.1.7 2.1.2 功能注册 注册功能节点,如图2.2.1.8,其中”对应文件名或控件名部分”,需填写模板jsp或者自定义JSP,并传入对应的控制类参数。 图2.2.1.8 3、模板初始化 图2.2.1.9 2.2
18、 快速开发第一个卡片单据 现在我们就可以很快的开发出一个基于元数据的标准卡片型单据。 1、 选择jsp模板,我们这个单据是卡片结构类型,所以采用cardui.jsp模板; 2、 设置pageId,这个值就是我们在NC模板中注册的此单据的模板类型。 3、继承DefaultCardController类,生成此单据的Controller,不同的界面类型需要继承不同Controller。 public String getBillType() (){// 需要返回单据模板号 return "13010301"; } 4、 完成以上的步骤,我们就可以开发出一个单据来: 启动
19、server,在浏览器中输入URL: http://localhost/{context_name}/core/cardui.jsp?pageId=13010301&ctrl=nc.lfw.billtemplate.controller.branch.TestBranchCardController OK,现在就可以看见单据页面了 2.3 定制功能 2.3.1 前台校验 前台校验主要处理对界面数据输入、修改、保存的校验,在这里只需要根据不同的业务,来进行前台校验,对于在元数据中设定的不需在此进行校验,这里只是对业务的自定义校验。 在每一个页面对应的include.js 中需实现
20、specialCellLogicCheck(key,value)方法,例如对2.2.3中的卡片单据。 /** * 前台业务校验 * @param dataet * @param value 比较值 * @param cellIndex 列号 * @return 如果校验规则通过则 返回null,否则返回string */ function specialCellLogicCheck(dataset,value,cellIndex){ var meta = dataset.metadata(); var key = meta[cellIndex].
21、key;
if( (key == “branch_code”) && (isCode(value)) )
return “此字段不能含以下字符 \/,?.[]()!@#$%^&*-=_+<>:';
return null;
}
当输入框失去焦点时,开始校验,校验不通过提示如下:
当保存时,校验不通过提示如下:
2.3.2 自定义按钮
如果需要在单据中设置自定义的按钮,则需要重写此单据Controller中的这个方法
public Map
22、 {
Map
23、tus(null); btnVo1.setCmd("mands.SelfDefCommand"); btnVo1.setHotKey("A"); btnVo1.setDisplayHotKey("(A)"); btnVo1.setModifiers(Event.CTRL_MASK); sefBtns.put(101, btnVo1); LfwButtonVO btnVo2 = new LfwButtonVO(); btnVo2.setBtnNo(1001); btnVo2.setBtnName("自定义子1"); bt
24、nVo2.setHintStr("这是自定义按钮子按钮1"); btnVo2.setOperateStatus(new int[]{IBillOperate.OP_ADD}); btnVo2.setBusinessStatus(null); btnVo2.setCmd("mands.SelfDefCommand"); btnVo2.setHotKey("B"); btnVo2.setDisplayHotKey("(Ctrl+Shift+B)"); btnVo2.setModifiers(Event.CTRL_MASK|Event.SHIFT_MASK);
25、 sefBtns.put(1001, btnVo2); btnVo1.setChildAry(new int[]{1001}); return sefBtns; } 这段代码是在单据页面中自定义了两个按钮,一个父一个子,如下图: 这些按钮的页面逻辑写在此单据的include.js中,实现 function selfDefBtnFuc(btnNo){ //btnNo 是在controller中定义的按钮号 if(btnNo == 101) //TODO 点击此按钮的处理逻辑 } 2.3.3 改变页面布局 LFW控件支持对页面布局的调整,您
26、可以自定义jsp模板,可以参照cardui.jsp 2.3.4自定义单据模板 Lfw支持一些特殊的页面显示设置,可以通过以下的配置来实现很多不同的单据表现 Form表单自定义项说明: 1. 自定义表单元素:如果formElement不是form元素本来支持的元素类型,是自定义类型,那么必须在单据模板中选中这个element,在该element的自定义三属性中添加”lfw_selfDefEle:true” 2. 表单元素colspan设置:如果要设置一个表单元素在form中跨几列,在自定义3中添加”lfw_colSpan:列数”键值对 3. 表单元素r
27、owspan设置:如果要设置一个表单元素在form中跨几行,在自定义3中添加”lfw_rowSpan:行数”键值对 4. 表单元素是文本域的设置:如果要使用TextArea作为表单元素,在自定义3中添加”lfw_ editorType: TextArea” 5. 表单元素默认几列:如果要设置表单元素默认显示几列,在页签属性的自定义3中添加“lfw_ formColumnCount:列数”,如果不设置默认采用3列布局 Grid表格控件自定义项说明: 1. 表格列编辑器类型设置:如果想用自定义编辑器编辑表格一列,必须在相应的单据模板元素自定义3中添加”lfw_ edit
28、orType:编辑类型”的键值对 2. 表格列渲染器类型设置:如果想用自定义渲染器渲染表格一列,必须在相应的单据模板元素自定义3中添加”lfw_renderType:渲染器类型”的键值对 3. 表格列是否可排序:如果要设置表格某列能否排序,必须在相应的单据模板元素自定义3中添加”lfw_ sortable:true|false”的键值对,默认列都能排序 4. 表格列是否自动扩展:表格列默认是不自动扩展的。如要列的宽度要自动扩展必须在单据模板元素自定义3中添加”lfw_autoExpand:true”。不设置自动扩展的列指定列多宽显示的时候就多宽,这样可能会出现如下效果: 列不能
29、充满整个表格,如果在单据模板中设置了三列均为自动扩展列, 表格控件的展示效果如下: 表格控件的前后台分页说明: 表格控件支持前后台分页,默认不分页,前后台分页不能同时设定 1. 设置前台分页要在页签属性的自定义3中添加”lfw_gridPageClientSize:每页记录数”。 2. 设置后台分页要在页签属性的自定义3中添加”lfw_ gridPageSize:每页记录数”。 注意:需要说明的是,如果字段的显示方式不需要在实施时由需求确定,推荐在单据对应的PageMode实现类中使用代码逻辑完成以上功能。模板设置只推荐在实施时确定格式的场景下使用。
30、 2.4 开发树卡型单据 NC web 模式化开发和NC模式化开发相类似,也提供了左树右卡的树卡型单据。 树的功能有导航树和业务树两种,不同的树需要继承不同的controller。继承结构如下图所示: AbstractTreeController AbstractTreeControllerNav AbstractTreeControllerBusi AbstractIdTreeControllerNav AbstractIdTreeControllerBusi DefaultIdTre
31、eManageControllerNav DefaultIdTreeCardControllerBusi 在实际的开发中可通过不同的业务来选择树的类型,如2.2.3中的卡片型单据我们选择业务树。 由于业务树不需要再组织树数据,可继承 DefaultIdTreeCardControllerBusi,并重载方法。 @Override public String getLabelFields() {//返回显示名 return "branch_name"; } @Override public String getPPkField() {//返回父键
32、 return "pid"; } @Override public String getPkField() {//返回主键 return "pk_branch"; } public String getBillType() {//返回模板号 return "10081313"; } public int getPageType() { return BillTemplateConstants.NORMAL;//返回单据类型 } public int[] getCardButtonAry(){//返回按钮 return n
33、ew int[]{ IBillButton.Add, IBillButton.Edit, IBillButton.Delete, IBillButton.Line, IBillButton.Save, IBillButton.Cancel, IBillButton.Refresh }; } OK,启动Server,打开IE,输入: http://localhost/{context}/core/treecardui.jsp?pageId=1301&ctrl=nc.lfw.billtemplate.controll
34、er.areacl.TestAreaClTreeController 2.5开发带审批流的卡片单据 1、不同于上面开发的单据,带审批流的单据controller需要实现IPFControllerBase接口。 2、实现以下方法: isEditInGoing() 返回布尔值,审批进行中是否可修改,系统默认不可修改。 isExistBillStatus() 返回是否存在单据状态。 getBillField() 返回IbillField类型,在流程平台中处理单据需要一些关于某些字段名称的信息,如果业务节点的数据库字段命名与UI工厂缺省情况相同,如:单据状态:vbillsta
35、tus,审批人:vapproveid,审批日期:dapprovedate,审批核语:vapprovenote,则不用实现该方法,系统直接使用默认字段设置。 2.6 变体一:单表体的卡片型单据 单表体的单据是指装载页面的数据只有表体。 制作这样的单据,和2.3.2中的很类似:设计元数据、注册NC模板,选择jsp模板等 继承DefaultIdTreeCardControllerBusi类,生成此单据的Controller 这个类需要重载以下几个基本方法: // 需要返回数节点的名称字段 public String getLabelFields
36、){ return “areaclname”; } // 返回父键 public String getPPkField(){ return “pk_fatherareacl”; } //返回主键 public String getPkField(){ return “pk_areacl”; } //返回在NC模板中注册的模板号 public int getPageType(){ return “10081315”; } //返回需要按钮 public int getCardButtonAry(){ ret
37、urn new int[]{ IBillButton.Add, IBillButton.save, IBillButton.Delete, IBillButton.Save, IBillButton.Cancel, IBillButton.Refresh } } 以上都和2.2.3相似,有一个方法必须重载 public int getPageType(){ //返回单据的类型,默认的是NORMAL return BillTemplateConstants.SINGLE_HEAD; } 5、 完成以上的步骤,我们就可以开发出一个单据来: 启动server,在浏览
38、器中输入URL: http://localhost/{context}/core/treecardui.jsp?pageId=1301&ctrl=nc.lfw.billtemplate.controller.areacl.TestAreaClTreeController OK,现在就可以看见单据页面了 2.7 变体二:多表头 多表头是指表头有多组数据可编辑,新增。 制作这样的单据,完成前期准备后,需继承DefaultMultiHeadCardController这个类,并重载一个方法 //返回单据模板号 public String getBillType() { re
39、turn "10081314"; } 如果需要调整页面的Form位置可重写这个方法 //默认返回1,form在表头下;返回0,form在上 public int getFormPosition() { return 1; } 2.8 多页签的卡片型单据 在卡片型单据中,有时会需要多子表或主表多页签,这些情况不需要特别的设置,只需在NC模板中定义好页面即可。 第五章 如何制作列表型单据 1.列表型单据页面示例 图暂缺 2列表型单据的开发 列表型单据可分为单表头,多表头,主子表三种变体类型。下面我们首先开发标准列表型单据。 首先建立元数
40、据模型,发布元数据,生成java源文件,在2.2.1中已经详细的介绍了元数据的建立及发布的步骤,在此我们开发一个订单合同的单据,元数据模型如图3.2.1, 图3.2.1 现在我们就可以很快的开发出一个基于元数据的标准列表型单据。 3、 选择jsp模板,我们这个单据是卡片结构类型,所以采用listui.jsp模板; 4、 设置pageId,这个值就是我们在NC模板中注册的此单据的模板类型。 3、继承DefaultListController类,这个类默认实现了大多数上层接口的方法, 这个类需要重载以下几个基本方法: public String getBillType
41、) {(){// 需要返回单据模板号 return " 13020301"; } 6、 完成以上的步骤,我们就可以开发出一个单据来: 启动server,在浏览器中输入URL: http://localhost/{context_name}/core/listui.jsp?pageId=13020301&ctrl=nc.lfw.billtemplate.controller.contract.TestContractListController OK,现在就可以看见单据页面了 3列表型单据的变体 列表型单据的变体的实现主要是实现不同的controller,列
42、表型单据controller的继承路径如下: DefaultListController DefaultMultiHeadListController 主子表、单表体的单据可以直接继承默认的controller,即DefaultListController,如果要实现多表头的列表型单据,可以继承DefaultMultiHeadListController 第六章 如何开发管理型单据 1管理型单据页面示例 图片暂缺 2管理型单据的开发 管理型单据可分为单表头,多表头,主子表三种变体类型。下面我们首先开发标准管理型单据。 首先
43、建立元数据模型,发布元数据,生成java源文件,在2.2.1中已经详细的介绍了元数据的建立及发布的步骤,在此我们开发一个订单合同的单据,元数据模型如图3.2.1, 现在我们就可以很快的开发出一个基于元数据的标准管理型单据。 5、 选择jsp模板,我们这个单据是卡片结构类型,所以采用manageui.jsp模板; 6、 设置pageId,这个值就是我们在NC模板中注册的此单据的模板类型。 3、继承DefaultManageController类,这个类默认实现了大多数上层接口的方法, 这个类需要重载以下几个基本方法: public String get
44、BillType() {// 需要返回单据模板号 return " 13020202"; } public int getPageType() {//单据类型,这里选择普通单据 return BillTemplateConstants.NORMAL; } public int[] getCardButtonAry() {//返回默认的管理型单据的按钮集,可以自定义 return BillTemplateConstants.MANAGE_CARD_BUTTON_ARR; } 7、 完成以上的步骤,我们就可以开发出一个单据来: 启动server,
45、在浏览器中输入URL: http://localhost/{context_name}/core/manageui.jsp?pageId=13020202&ctrl=nc.lfw.billtemplate.controller.test.custinfo.TestCustBasCardController OK,现在就可以看见单据页面了 3 管理型单据的变体 管理型单据的变体的实现主要是实现不同的controller,列表型单据controller的继承路径如下: DefaultManageController DefaultMultiHeadManageController 主子表、单表体的单据可以直接继承默认的controller,即DefaultManageController,如果要实现多表头的列表型单据,可以继承DefaultMultiHeadManageController






