应该说eXtremeComponents已经实现了一些较为完善的功能,包括排序、过滤等,现在还支持Ajax功能。
用户通过设置标签(如table,row,column等)的属性(大部分的属性和html中的table、tr,td等的属性相同,另外添加一些用于控制的属性)便可轻松实现数据的列表。ec的强大还在于其良好的可扩展性,因为用户可以方便地对其进行二次开发,以满足一些特殊的需求。
由于本文主要是分析ec的代码设计而不是使用说明,因此如何使用ec可以参考相关的指南和参考文档。以下仅列举了发行包中的一个例子test.jsp:(大部分属性的含义都很明显,这里也不作说明了)
以下内容为程序代码:
<ec:table
items="pres"
action="${pageContext.request.contextPath}/test.jsp"
imagePath="${pageContext.request.contextPath}/images/table/*.gif"
title="test"
width="60%"
rowsDisplayed="5"
>
<ec:exportXls fileName="resourceList.xls" tooltip="Export Excel"/>
<ec:exportPdf fileName="resourceList.pdf" tooltip="Export PDF"/>
<ec:row highlightRow="true">
<ec:column property="rowcount" cell="rowCount" onClick="alert('${pres[0]}');"
title="No." sortable="false" filterable="false" width="5%"
style="text-align:center"/>
<ec:column property="name" title="姓名" href="#" filterCell="droplist"/>
<ec:column property="nickname" title="昵称" filterable="true" sortable="false"/>
<ec:column property="term"/>
</ec:row>
</ec:table>
ec的精彩点之一是Limit接口及其实现类。
就整个软件的设计架构来说,ec也是非常优秀的。ec完全面向对象,并充分运用了设计模式,重构后的整个代码简洁且高效。
1. 代码结构
1.1四个第一级包:
package org.extremecomponents.table.*;(列表)
package org.extremecomponents.test;(用于测试)
package org.extremecomponents.tree;(树型,尚处于开发中)
package org.extremecomponents.util;(工具类)
其中,util包下的HtmlBuilder类封装了视图输出(如html格式)的各种操作,如函数table()用于输出一个HTML标签<table,该类包含一个StringWriter的私有变量。
而ExtremeUtils类则封装了一些常用的函数,如函数formatDate和formatNumber等。
1.2table包下的主要内容:
(1)package org.extremecomponents.table.bean;
说明:简单的bean类,类似VO。
Attributes抽象类包含一个HashMap的私有变量,用于关联属性及其值。类Column,Export,Row,Table都继承了Attributes并添加了各种的属性变量,如Column中的style变量便是对应HTML中td的style属性,Table中的border变量对应的是HTML中table的border属性,等等。当然,这些bean中还有一些属性是用于控制的,Column中的cell变量用于指明该单元格的输出是用哪个Cell类(其实就是用于控制输出的格式),如果cell=”date”,那么该单元格的输出就采用DateCell类的输出,即日期格式;等等。
还有ColumnDefaults,ExportDefault,RowDefault,TableDefault这几个最终类用于对应的Column,Export,Row,Table初始化时设置一些默认的属性值。这些默认值是由core包下的extremetable.properties文件设置的,在TableModel初始化时通过类TableProperties来读取的。
(2)package org.extremecomponents.table.calc;
说明:用于列的属性(calc,和calcTitle)中,由多个列的值计算而成的值。如:总值,平均值等。
Calc接口,只定义了一个函数getCalcResult(model,column);
类AverageCalc和TotalCalc实现了该接口,分别计算平均值和总值。
(3)package org.extremecomponents.table.callback;
说明:用于检索、过滤和排序行集数据,由TableModel中的execute方法调用。
三个接口:RetrieveRowsCallback, FilterRowsCallback, SortRowsCallback分别定义了函数retrieveRows(model),filterRows(model),sortRows(model),用于检索、过滤和排序数据。FilterRowsCallback的默认实现是得到Beans或Maps的Collection,然后通过实现jakarta Predicate接口来进行过滤。
ProcessRowsCallback类实现了这三个接口,也是ec中默认的对数据进行检索,过滤和排序的类。但是,这种功能的正确实现是基于这么一个事实,即ec得到的数据必须是数据库中未经处理(即过滤或排序)的所有原始数据,否则过滤或排序等处理的结果便不是正确的。还有,ec也能处理数据的分页,但现实中我们的数据量往往都很大(成千上万的),不可能未经处理就把所有的数据全部读出让ec来处理!显然,分页、过滤、排序等等处理都是程序在和数据库交互中完成的,ec仅仅接受处理后的数据然后显示而已。而LimitCallback类便实现了这种处理方案,它也实现了上面的那三个接口,但仅仅是直接返回数据而已。这种“Limit”的实现在limit包中再做讨论。
(4)package org.extremecomponents.table.cell;
说明:用于单元格的格式化输出。
Cell接口定义了两个方法:getExportDisplay和getHtmlDisplay。
AbstractCell抽象类实现了Cell,其实也定义了cell的输出框架,其继承类只需实现getCellValue方法即可。其getHtmlDisplay方法实现如下:
public String getHtmlDisplay(TableModel model, Column column) {
ColumnBuilder columnBuilder = new ColumnBuilder(column);
columnBuilder.tdStart();
columnBuilder.tdBody(getCellValue(model, column));
columnBuilder.tdEnd();
return columnBuilder.toString();
}
DisplayCell继承了AbstractCell,是ec中默认的cell。
DateCell继承了AbstractCell,用于输出日期格式化的单元格。
FilterCell实现了Cell,用于在头部输出一个用于过滤的输入框。
FilterDroplistCell实现了Cell,用于在头部输出一个用于过滤的下拉列表。
HeaderCell实现了Cell,用于输出头部标题的单元格内容。
NumberCell继承了AbstractCell,用于输出数字格式化的单元格。
RowCountCell继承了AbstractCell,用于输出数据集合的序号。
SelectAllHeaderCell实现了Cell,用于在头部生成一个选择框,用于选择所有的数据。
(5)package org.extremecomponents.table.context;
说明:ec中用到的上下文类的封装。
Context接口,用于获得Application,Page,Session,Request等上下文的变量。
HttpServletRequestContext实现了Context接口。
ServletRequestContext实现了Context接口。
JspPageContext实现了Context接口。TableModel初始化时(在TableTag中)使用了该类:
model = new TableModelImpl(new JspPageContext(pageContext), TagUtils.evaluateExpressionAsString("locale", this.locale, this, pageContext));
(6)package org.extremecomponents.table.core;
说明:ec列表的核心包,包括表格模型,配置文件,属性文件,参数封装等。
Registry接口,处理所有的参数(),包括用户自定义的。
AbstractRegistry抽象类实现了Registry,保存一些ec的内部参数及用户参数。
TableRegistry继承了AbstractRegistry类,由TableModel中的addTable函数调用。
Messages接口,即支持国际化显示,从Local中(如ZH_CN)获取正确的资源文件。由resource包中的TableResourceBundle类实现。在TableModelImp中初始化:
Messages messages = TableModelUtils.getMessages(this);
messages.init(context, this.locale);
this.messages = messages;
Preferences接口,用于获取配置文件中的设置值。
TableProperties实现了Preferences,初始化时先加载系统默认的配置文件,然后再加载由用户自己配置的文件,如下:
public void init(Context context, String preferencesLocation) {
try {
properties.load(this.getClass().getResourceAsStream(EXTREMETABLE_PROPERTIES));
if (StringUtils.isNotBlank(preferencesLocation)) {
InputStream input = this.getClass().getResourceAsStream(preferencesLocation);
if (input != null) {
properties.load(input);
}
}
} catch (IOException e) {
if (logger.isErrorEnabled()) {
logger.error("Could not load the eXtremeTable preferences.", e);
}
}
}
其在TableModelImpl中被调用:
Preferences preferences = new TableProperties();
preferences.init(context, TableModelUtils.getPreferencesLocation(context));
this.preferences = preferences;
为了设置属性文件,你应该如下例所示在/WEB-INF/web.xml文件中声明一个context-param,并 指定你的属性文件的路径:
<context-param>
<param-name>extremecomponentsPreferencesLocation</param-name> <param-value>/org/extremesite/resource/extremecomponents.properties</param-value>
</context-param>
TableCache类用于获得一些缓存的对象,包括Cell,State,Callback,Interceptor等,因此这些类都是singleton,并且不再线程安全。
TableModel接口,是系统的核心接口,包括其实现类TableModelImpl,因为它们把系统中的所有变量都联系了起来。
TableModelImpl实现了TableModel,初始化时获取Context,Preferences及Messages实例;通过addTable函数获取Registry及LimitFactory,Limit实例。
定义的变量有:Context,Preferences,Messages,Registry, TableHandler,RowHandler,ColumnHandler,ViewHandler,ExportHandler,Limit,Locale等。
变量currentRowBean保存当前处理的bean,并在上下文中设置var变量(table中的var属性)的值指向该bean,这样的话,Row和Column标签中便可以通过var变量来应用这个当前的bean对象,获得一些有意义的值。如下:
public void setCurrentRowBean(Object bean) {
int rowcount = rowHandler.increaseRowCount();
this.currentRowBean = bean;
context.setPageAttribute(TableConstants.ROWCOUNT, String.valueOf(rowcount));
context.setPageAttribute(tableHandler.getTable().getVar(), bean);
}
而collectionOfBeans、collectionOfFilteredBeans、collectionOfPageBeans则分别保存了所有的bean、过滤后的bean、当前页的bean。
TableModelImpl中的execute函数在标签第一次迭代时被调用,先过滤,后排序,然后通过ViewHandler.setView()来设置输出的视图。
(7)package org.extremecomponents.table.filter;
说明:过滤器,用于导出时的过滤,实现了javax.servlet.Filter。
(8)package org.extremecomponents.table.handler;
说明:各种处理句柄,帮助TableModel处理对应的bean,即关联model和bean。
类有:ColumnHandler, ExportHandler, RowHandler, TableHandle, ViewHandler。
(9)package org.extremecomponents.table.interceptor;
说明:拦截器,用于运行时添加和修改对应bean的属性。
接口有:TableInterceptor, RowInterceptor, ColumnInterceptor, ExportInterceptor。
用户可以实现自己的Interceptor,然后在对应的标签中使用Interceptor属性来设置并使用。所有的拦截器接口都定义了一个add方法, add方法被用来处理模型bean第一次创建时的属性。行和列的拦截器还有一个modify 方法,在当行和类进行处理是对属性值进行操作。
(10)package org.extremecomponents.table.limit;
说明:封装排序,过滤及分页的一些信息,用于向后台程序传递Limit对象。
LimitFactory接口,Limit的工厂接口。
AbstractLimitFactory抽象类实现LimitFactory,用于获取是否导出、当前页面数、排序字段及值及过滤集合等。
TableLimitFactory继承了AbstractLimitFactory。
ModelLimitFactory也继承了AbstractLimitFactory。
Filter最终类,值对象,三个String型私有变量:alias, property, value。
FilterSet类,内含一个Filter数组。
Limit接口,定义了一些用于获取limit信息的函数,如排序值、过滤字段及值、等等。
TableLimit最终类,实现了Limit。其构造函数的参数是LimitFactory,即Limit的值是由工厂类得到的。
Sort最终类,值对象,三个String型私有变量:alias,property,value。
(11)package org.extremecomponents.table.resource;
说明:资源文件及操作资源的类。
TableResourceBundle实现了Messages接口,初始化时会加载特定的资源文件以及用户自定义的资源文件,通过在web.xml中定义extremecomponentsMessagesLocation值来获取。
public void init(Context context, Locale locale) {
this.locale = locale;
defaultResourceBundle = findResourceBundle(EXTREMETABLE_RESOURCE_BUNDLE, locale);
String messagesLocation = TableModelUtils.getMessagesLocation(context);
if (StringUtils.isNotBlank(messagesLocation)) {
customResourceBundle = findResourceBundle(messagesLocation, locale);
}
}
(12)package org.extremecomponents.table.state;
说明:处理表格的状态。
State接口,定义了saveParameters和getParameters两个函数。
AbstractState抽象类,实现了State接口,定义了saveParameters函数。
DefaultState类实现了State接口,默认两个函数为空。
(13)package org.extremecomponents.table.tag;
说明:标签类,是ec开始的地方。
ColumnsTag继承TagSupport,用于生成自动产生的类。
ColumnTag继承BodyTagSupport并实现了ColumnInterceptor拦截器。
首次迭代时并不生成视图代码,而是:
Column column = new Column(model);
//设置一些属性。。。
addColumnAttributes(model, column);
model.getColumnHandler().addColumn(column);
第2次迭代开始后便执行真正的视图输出:
if (column != null) { // null if view not allowed
Object bean = TagUtils.getModel(this).getCurrentRowBean();
Object propertyValue = TableModelUtils.getColumnPropertyValue(bean, property);
column.setValue(getColumnValue(propertyValue));
column.setPropertyValue(propertyValue);
modifyColumnAttributes(model, column);
model.getColumnHandler().modifyColumnAttributes(column);
model.getViewHandler().addColumnValueToView(column);
}
最后那个语句的函数代码如下:
public void addColumnValueToView(Column column) {
Cell cell = TableModelUtils.getCell(column);
boolean isExported = model.getLimit().isExported();
if (!isExported) {
column.setCellDisplay(cell.getHtmlDisplay(model, column));
} else {
column.setCellDisplay(cell.getExportDisplay(model, column));
}
getView().body(model, column);
}
通过getView().body()函数的调用便完成了视图的输出。
RowTag继承了TagSupport并实现了RowInterceptor。
和ColumnTag类似,首次迭代时也仅仅是new一个Row对象,然后设置属性并添加到model中。但RowTag并不产生视图的输出,而是在ColumnTag视图输出时判断是否第一个或最后一个Column,若是,则这时才输出Row的视图数据。如下(抽象类AbstractHtmlView中:)
public void body(TableModel model, Column column) {
if (column.isFirstColumn()) {
rowBuilder.rowStart();
}
html.append(column.getCellDisplay());
if (column.isLastColumn()) {
rowBuilder.rowEnd();
}
}
TableTag继承了TagSupport,实现TryCatchFinally和TableInterceptor接口。
在doStartTag()函数中:
初始化TableModel的实例为TableModelImpl类,再实例化一个Table类并设置属性,最后通过model.addTable(table)把Table添加到model中,在该addTable函数中完成TableRegistry和TableLimit的初始化。
在doAfterBody()函数中:
在doEndTag()函数中:
pageContext.getOut().println(model.getViewData());
以上这语句便完成了视图的输出,而model.getViewData()函数的代码如下:
public Object getViewData() throws Exception {
Object viewData = viewHandler.getView().afterBody(this);
if (limit.isExported()) {
context.setRequestAttribute(TableConstants.VIEW_DATA, viewData);
context.setRequestAttribute(TableConstants.VIEW_RESOLVER, exportHandler.getCurrentExport().getViewResolver());
context.setRequestAttribute(TableConstants.EXPORT_FILE_NAME, exportHandler.getCurrentExport().getFileName());
return "";
}
return viewData;
}
还有几个Tag:ExportCsvTag, ExportPdfTag, ExportTag, ExportXlsTag,ParameterTag.等。
而TagUtils类则封装了几个处理函数,如利用ExpressionEvaluatorManager类完成属性的设置?
(14)package org.extremecomponents.table.view;
说明:视图部分,包括HTML,toolbar,pdf,xsl等
View接口,定义三个函数:beforeBody, body, afterBody。
AbstractHtmlView抽象类实现了View,其实也便定义了表格输出的框架,继承类只需实现beforeBodyInternal和afterBodyInternal两个函数即可,分别用于输出表格的表头数据及表尾数据,而其body函数则由ColumnTag标签处理时调用。在beforeBody函数中,该抽象类实例化HtmlBuilder、FormBuilder、TableBuilder、RowBuilder等用于构建相应视图的类,如FormBuilder完成Html中form表单等参数的设置等。
HtmlView继承了AbstractHtmlView类,是ec中默认的视图。
(15)package org.extremecomponents.table.view.html;
说明:用于帮助视图构建输出的类,如ColumnBuilder,FormBuilder,RowBuilder,TableBuilder等,如ColumnBuilder.tdEnd()函数生成的代码是“</td>”。
TableActions类封装了一些js的动作代码,主要用于form动作。
(16) package org.extremecomponents.table.html.toobar;
说明:工具条,类型有:按钮,字符,图形等。
ToolbarItem接口,
AbstractItem抽象类。
ButtonItem,ImageItem,TextItem继承AbstractItem实现了ToolbarItem接口。
|
|