ec是一系列提供高級顯示的開源JSP定制標簽,當前的包含的組件為eXtremeTable,用于以表形式顯示數據。ec現在的版本是1.0.1,由Jeff Johnston開發的,網址:
http://www.extremecomponents.org。
應該說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接口。
Lyyb2001