现在来看看63. XPages自定义控件(三)高级搜索之一中提到的RecordView自定义控件的代码。从控件的设计视图自上而下可以看到用于条件查询的控件、一个向用户反馈操作消息的Message自定义控件、常见的视图操作的几个按钮和一个XPages自带的视图控件。
RecordView控件的设计界面
RecordView控件从功能上分成的这四个部分,我们来一一检视:
用于条件查询的控件
RecordView控件上的多条件查询包括员工姓名和打卡的起始日期。员工姓名的输入又采用了另一个自定义控件InputUser,在用户键入姓名时提供员工姓名列表的辅助输入(type ahead)。InputUser控件的界面代码如下:<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
style="display:inline; ">
<xp:inputText id="Users" style="float:none;width:auto">
<xp:typeAhead mode="full" minChars="1" ignoreCase="true"
id="typeAhead1">
<xp:this.valueList><![CDATA[${app.usersOptions}]]></xp:this.valueList>
</xp:typeAhead>
</xp:inputText>
</xp:view>
辅助输入的值列表用表达式语言(expression language)设置,来自一个类名为starrow.xsp.App的managed bean的getUsersOptions()方法:
public List getUsersOptions() throws NotesException{
Vector result=new Vector();
try {
//获得HR数据库的路径
String path = getHRDBPath();
//XSPUtil是笔者编写的一个包含很多常用方法的工具类。
Database db=XSPUtil.getDatabase(path);
result=db.getView("bySCRO").getColumnValues(1);
} catch (NotesException e) {
if (e.id==NotesError.NOTES_ERR_DBNOACCESS){
XSPUtil.feedback(e.text);
}else{
throw e;
}
}
return result;
}
起始日期使用了两个XPages日期控件。其后的三个按钮,一个是用于测试,读取和显示从InputUser控件获得的用户名,另两个分别用于搜索和清除结果,XPage的代码片断如下:<xp:button value="Search" id="btnSearch">
<xp:eventHandler event="onclick" action="#{app.search}"
submit="true" refreshMode="complete">
</xp:eventHandler>
</xp:button>
<xp:button value="Clear" id="button1">
<xp:eventHandler event="onclick" action="#{app.clearSearch}"
submit="true" refreshMode="complete">
</xp:eventHandler>
</xp:button>
可以看到两个按钮的onclick事件的响应程序也都是用表达式语言绑定到同一个managed bean里,不同的是这里采用的是表示动态计算的#符号而不是上面的InputUser控件用到的设定页面载入时记算的$符号,这并不是笔者的有意选择,而是因为如果选择更高效的页面载入时计算,XPages会报错。
public void search(){
//获得XPages视图控件
XspViewPanel viewPanel1 = (XspViewPanel) JSFUtil.findComponent("viewPanel1");
//获得视图控件绑定的的数据源
DominoViewData vd=(DominoViewData) viewPanel1.getData();
//为方便构造查询条件,创建一个字符串的Collection
Collection<String> filters=new Vector<String>();
String filter="";
//获得RecordView自定义控件
UIIncludeComposite cc=(UIIncludeComposite) JSFUtil.findComponent("recordView1");
//cc.getPropertyMap().getString("filter")
//com.ibm.xsp.FacesExceptionEx: Null value for composite component property filter
//如果设置了该控件的filter属性,则将属性值添加到查询条件里。
if (cc.getPropertyMap().get("filter")!=null){
filters.add("("+cc.getPropertyMap().get("filter")+")");
}
//获得输入起始时间的编辑框
XspInputText inpFrom=(XspInputText) JSFUtil.findComponent("inpFrom");
//获得起始时间值,格式化,并用它构造出用于全文搜索的语句
if (inpFrom.getValue()!=null && !inpFrom.getValue().equals("")){
SimpleDateFormat df=new SimpleDateFormat("MM/dd/yy");
Date from=(Date) inpFrom.getValue();
filters.add("([AccessTime]>="+df.format(from)+")");
}
//类似地处理结束时间
XspInputText inpTo=(XspInputText) JSFUtil.findComponent("inpTo");
if (inpTo.getValue()!=null && !inpTo.getValue().equals("")){
SimpleDateFormat df=new SimpleDateFormat("MM/dd/yy");
Date to=(Date) inpTo.getValue();
filters.add("([AccessTime]<="+df.format(to)+")");
}
//获得输入用户的编辑框,构造出查询语句
XspInputText inpUser=(XspInputText) JSFUtil.findComponent("Users");
if (!inpUser.getValue().equals("")){
filters.add("([StaffName]="+inpUser.getValueAsString()+")");
}
//合并出最终的查询语句,传递给Message自定义控件
filter=starrow.Commons.join(filters, " & ");
XSPUtil.getRequestMap().put("message", filter);
//设置数据源的搜索条件,即搜索。
vd.setSearch(filter);
}
public void clearSearch(){
//清空各个输入查询条件的编辑框
((XspInputText) JSFUtil.findComponent("inpFrom")).setValue("");
((XspInputText) JSFUtil.findComponent("inpTo")).setValue("");
((XspInputText) JSFUtil.findComponent("Users")).setValue("");
//调用搜索方法
this.search();
}
Message自定义控件
15. 如何在XPages中提示操作成功一文里介绍了在XPages中向用户提供操作结果反馈的两类方式,其中的第二种方式——在页面的某个部分显示消息——是现在流行的风格。很容易将这项功能封装在一个自定义控件里,这样在任何页面也只需在想要的位置放置,再在运行业务逻辑结束后将给用户的提示传递到指定的RequestScope的Message变量就可以了。上面的search()方法就运用了这一点。<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:text escape="true" id="message" value="#{requestScope.message}"
style="background-color:rgb(255,255,0)">
</xp:text>
</xp:view>
反馈信息的格式,如字体、颜色和背景等都可以在控件里预先设置,整个应用程序里各处的反馈信息自动具有了统一的风格。
常见的视图操作的几个按钮
这些按钮是刷新、新建和删除,是最常见的视图操作。普通的刷新只需要用JavaScript重新载入当前页面或者在XPages的语境下提交一个空操作。而我们这里的RecordView控件显示的不是一个Notes视图而是一个视图的全文搜索的结果,所以在点击刷新按钮时还更新了数据库的全文索引。<xp:button value="Refresh" id="button5">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:database.updateFTIndex(false);}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
新建按钮使用浏览器端JavaScript打开一个新页面。<xp:button value="New" id="button3" rendered="true">
<xp:eventHandler event="onclick" submit="false">
<xp:this.script><![CDATA[window.open("record.xsp", "_blank");]]></xp:this.script>
</xp:eventHandler>
</xp:button>
删除按钮为了简单直接调用了XPages提供的删除文档的简单操作(simple action)。
<xp:button value="Delete" id="button4">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:deleteSelectedDocuments view="viewPanel1"
message="Are you sure to delete the selected records?">
</xp:deleteSelectedDocuments>
</xp:this.action>
</xp:eventHandler>
</xp:button>
XPages视图控件
这个视图控件的设置和普通页面上的没有什么区别,除了其中的数据源和视图列的viewName、search、showCheckbox等属性绑定到了RecordView控件的自定义属性。 <xp:viewPanel rows="20" id="viewPanel1" viewStyle="width:99%"
pageName="/record.xsp">
<xp:this.facets>
<xp:pager partialRefresh="true" layout="Previous Group Next"
xp:key="headerPager" id="pager1">
</xp:pager>
<xp:pager partialRefresh="true" layout="Previous Group Next"
xp:key="footerPager" id="pager2">
</xp:pager>
</xp:this.facets>
<xp:this.data>
<xp:dominoView var="view1"
viewName="${javascript:compositeData.notesView;}"
search="#{javascript:compositeData.filter;}" sortOrder="descending"
sortColumn="$8">
</xp:dominoView>
</xp:this.data>
<xp:viewColumn id="viewColumn8" columnName="$8"
showCheckbox="${javascript:compositeData.showEdit}">
<xp:this.displayAs><![CDATA[${javascript:if (compositeData.showEdit) return "link";}]]></xp:this.displayAs>
<xp:this.facets>
<xp:viewColumnHeader xp:key="header"
id="viewColumnHeader8" value="Date">
</xp:viewColumnHeader>
</xp:this.facets>
<xp:this.converter>
<xp:convertDateTime type="date"></xp:convertDateTime>
</xp:this.converter>
</xp:viewColumn>
<xp:viewColumn columnName="StaffName" id="viewColumn4">
<xp:viewColumnHeader value="Staff Name"
id="viewColumnHeader4">
</xp:viewColumnHeader>
</xp:viewColumn>
<xp:viewColumn columnName="AccessTime" id="viewColumn1">
<xp:this.converter>
<xp:convertDateTime type="time"></xp:convertDateTime>
</xp:this.converter>
<xp:viewColumnHeader value="Access Time"
id="viewColumnHeader1">
</xp:viewColumnHeader>
</xp:viewColumn>
<xp:viewColumn id="viewColumn9" columnName="Type">
<xp:this.facets>
<xp:viewColumnHeader xp:key="header"
id="viewColumnHeader9" value="Type">
</xp:viewColumnHeader>
</xp:this.facets>
</xp:viewColumn>
<xp:viewColumn columnName="CardNo" id="viewColumn2"
rendered="false">
<xp:viewColumnHeader value="Card Number"
id="viewColumnHeader2">
</xp:viewColumnHeader>
</xp:viewColumn>
<xp:viewColumn columnName="StaffNo" id="viewColumn3"
rendered="false">
<xp:viewColumnHeader value="Staff Number"
id="viewColumnHeader3">
</xp:viewColumnHeader>
</xp:viewColumn>
<xp:viewColumn columnName="Department" id="viewColumn5">
<xp:viewColumnHeader value="Department"
id="viewColumnHeader5">
</xp:viewColumnHeader>
</xp:viewColumn>
<xp:viewColumn columnName="Office" id="viewColumn7">
<xp:viewColumnHeader value="Office"
id="viewColumnHeader7">
</xp:viewColumnHeader>
</xp:viewColumn>
</xp:viewPanel>
自定义属性
在63. XPages自定义控件(三)高级搜索之一里,我们看到使用RecordView控件的几个页面在操作和视图显示上都有不同,在异常记录和考勤记录页面上可以使用条件查询,在补卡记录页面上可以新建和选择、删除记录。这些差异都是靠RecordView控件的自定义属性控制的。同理,决定控件展示的文档列表的Notes视图名和全文搜索的语句也是由自定义属性传递。
RecordView控件的自定义属性的设计视图
比如在显示考勤记录的页面里,关于RecordView控件的代码如下:
<xc:RecordView id="recordView1" notesView="vwRecord"
showSearch="${javascript:app.isAdmin()}">
<xc:this.filter><![CDATA[${javascript:var filter="[Type] is present";
if (!app.isAdmin()){
filter+=" & [StaffName]=" + context.getUser().getCommonName();
}
return filter;}]]></xc:this.filter>
</xc:RecordView>
在显示补卡记录的页面里,传递给RecordView控件的属性值就有了变化:
<xc:RecordView id="recordView1" notesView="vwRecord"
showSearch="${javascript:false;//app.isAdmin()}" showLink="true"
showCheckbox="true" showEdit="${javascript:app.isAdmin()}">
<xc:this.filter><![CDATA[${javascript:var filter="[MakeUp]=1";
if (!app.isAdmin()){
filter+=" & [StaffName]=" + context.getUser().getCommonName();
}
return filter;}]]></xc:this.filter>
</xc:RecordView>
app.isAdmin()是managedbean里一个判断当前用户是否管理员的方法,如果是则显示所有员工的信息,否则只显示当前用户自己的信息。
RecordView控件的各个组成部分都已介绍完毕,下一篇文章会给出它们的完整代码,并介绍使用这个自定义控件遇上的问题和反映出来的XPages的缺陷。