|
Posted on 2008-03-04 22:19 skycity 閱讀(600) 評論(0) 編輯 收藏 所屬分類: J2EE技術
列的filterCell屬性控制過濾器如何顯示,它和cell屬性非常相像并且也是實現Cell接口。馬上要定義的是默認的和droplist這兩個過濾器cells。 默認的是一個輸入框元素而droplist是一個下拉列表元素。當然,如果你需要進行一些定制你可以插接自己的實現。
最近,我被問到是否能夠實現一個過濾器cell,顯示已經通過別的過濾器過濾得到數據子集。答案當然是肯定的,而且這是我將在這里示范的。通常定制的 cell可以很容易被創建,這個示例將印證這點。在這個示例里last name列里顯示的將是通過first name過濾后的子集。如果沒有通過 first name過濾那么所有值都將被顯示。
通常你只需要為過濾器cell實現Cell接口。然而,因為我們要創建的過濾器cell是一個下拉列表,我們可以通過擴展 FilterDroplistCell來獲得它已經提供的很多功能,FilterDroplistCell是發行包已經提供的cells之一。
我們需要覆蓋FilterDroplistCell的唯一方法是getFilterDropList()。這是整個類的全部代碼:
public class FilteredDroplistCell extends FilterDroplistCell { private static Log logger = LogFactory.getLog(FilterDroplistCell.class);
protected List getFilterDropList(TableModel model, Column column) { List droplist = new ArrayList();
String firstNameFilter = model.getLimit().getFilterSet().getValue("firstName");
Collection beans = model.getCollectionOfBeans(); for (Iterator iter = beans.iterator(); iter.hasNext();) { Object bean = iter.next(); try { String firstName = BeanUtils.getProperty(bean, "firstName"); if (StringUtils.isNotBlank(firstNameFilter) && !firstName.equals(firstNameFilter)) { continue; }
String lastName = BeanUtils.getProperty(bean, column.getProperty()); if ((lastName != null) && !droplist.contains(lastName)) { droplist.add(lastName); } } catch (Exception e) { logger.debug("Problems getting the droplist.", e); } }
Collections.sort(droplist);
return droplist; } }
如果你比較這個類和父類,你會發現它們只有微小的區別。
首先需要注意的是我們需要找出first name是否已經被過濾了。
String firstNameFilter = model.getLimit().getFilterSet().getValue("firstName");
然后我們需要判斷當前bean的first name值是否和first name過濾器值相同。如果相同,將當前的last name值 添加到droplist中。
String firstName = BeanUtils.getProperty(bean, "firstName"); if (StringUtils.isNotBlank(firstNameFilter) && !firstName.equals(firstNameFilter)) { continue; }
如果last name將添加到droplist中,我們需要檢查droplist中是否已經包含了這個值。如果沒有,我們就把它添加到droplist中。
String lastName = BeanUtils.getProperty(bean, column.getProperty()); if ((lastName != null) && !droplist.contains(lastName)) { droplist.add(lastName); }
為了使用這個Cell你應該在Preferences中聲明一個別名。 當然,你可以省略這步而在JSP中提供這個Cell實現類的全路徑,但是使用Preferences更簡潔。
column.filterCell.filteredDroplist=org.extremesite.cell.FilteredDroplistCell
在ColumnTag通過設置filterCell屬性來使用FilteredDroplistCell。
<ec:column property="lastName" filterCell="filteredDroplist"/>
如果不清楚Preferences和ColumnTag定義語法請參考Preferences指南。
FilterRowsCallback被用來過濾傳給eXtremeTable的Beans的Collection。 FilterRowsCallback的默認實現是得到Beans或Maps的Collection,然后通過實現jakarta Predicate接口來進行過濾。當然,如果你需要進行一些定制你可以插接自己的實現。
首先聲明,本示例代碼包含一些從原包中剪切、粘貼的代碼(雖然不是很多)。在 最初的最終發行包之后,值過濾得到進一步改善使得更具復用性并更容易實現,可能和定制cell代碼行數相同。 當然,我被要求并非常樂意示范如何在當前代碼基礎上實現定制過濾。這有非常清晰的hooks實現,并且很容易實現。
本示例示范了如何調整代碼為過濾器提供一個精確的比較功能。當前的實現是通過使用StringUtils.contains()方法進行模糊比較。 本示例將使用StringUtils.equals()方法。你可以按照你的需要來調整代碼進行更多定制。
1.1.?定制FilterRowsCallback示例
首先你需要做的是創建一個實現Predicate接口的定制類。Predicate要求我們實現evaluate()方法來判斷是否包含 當前bean。因為你僅僅調整現在已有的功能,首先得到filterPredicate的源代碼(在發行包的callback包下), 拷貝到你的工程里。然后向下面展示的一樣將 StringUtils.contains()方法修改為StringUtils.equals()方法:
public final class ExactMatchFilterPredicate implements Predicate { private boolean isSearchMatch(String value, String search) {
...
else if (StringUtils.equals(value, search)) { return true; }
...
} }
然后我們需要實現和Predicate共同作用的FilterRowsCallback接口。再一次從發行包的callback包下拷貝ProcessRowsCallback源代碼到你的工程里。 請參照我們創建的定制的ExactMatchFilterPredicate 類來確認僅僅實現了FilterRowsCallback和修改Predicate。
public class ExactMatchFilterRows implements FilterRowsCallback { public Collection filterRows(TableModel model, Collection rows) throws Exception {
...
if (filtered) { Collection collection = new ArrayList(); Predicate filterPredicate = new ExactMatchFilterPredicate(model); CollectionUtils.select(rows, filterPredicate, collection);
return collection; }
...
} }
為了使用這個FilterRowsCallback你應該在Preferences中聲明一個別名。當然,你可以省略這步而在JSP中提供這個FilterRowsCallback實現類的全路徑,但是使用Preferences更簡潔。
table.filterRowsCallback.exactMatch=org.extremesite.callback.ExactMatchFilterRows
在TableTag通過設置filterRowsCallback屬性來使用ExactMatchFilterRows。
<ec:table filterRowsCallback="exactMatch"/>
如果不清楚Preferences和ColumnTag定義語法請參考Preferences指南。
eXtremeTable本質上是一個form組件,所以我假定表被包在form里,所有的功能都被認為是對form元素的操作。如果你想在表體中包含一些定制的form元素, 或者想將eXtremeTable嵌入到另外的form中,那么你就要使用表標簽的form屬性用來參照最近的form。
為了示范form特性,我們要做的工作將分解為JSP,Cell和Controller。
下面列出的是checkbox示例的完整代碼。想要強調的主要事情是表標簽form屬性設置為presForm,它參照被稱為presForm的form元素。
同時請注意表標簽的autoIncludeParameters屬性。進行排序、過濾、分頁時,默認的eXtremeTable將保持所有傳至JSP頁面的參數。 這個特性對于內部其他的form進行排序、過濾、分頁時,用于高效復制form元素同樣有效。可以設置 autoIncludeParameters屬性為false來固定它。
在這個form使用id屬性是因為xhtm標準的要求,同時你也可以使用form的name屬性。
<form id="presForm" action="<c:url value="selectedPresidentsListedController.run"/>" method="post">
Enter your name: <input type="text" name="userName" style="font-family:verdana,arial,helvetica,sans-serif;font-size:11px;" value="<c:out value="${param.userName}"/>" />
<ec:table items="presidents" action="${pageContext.request.contextPath}/selectedPresidentsController.run" view="compact" imagePath="${pageContext.request.contextPath}/images/table/compact/*.gif" rowsDisplayed="8" autoIncludeParameters="false" form="presForm" > <ec:exportPdf fileName="output.pdf" tooltip="Export PDF" headerColor="black" headerBackgroundColor="#b6c2da" headerTitle="Presidents" /> <ec:row> <ec:column alias="checkbox" title=" " width="5px" filterable="false" sortable="false" viewsAllowed="compact" cell="selectedPresident" /> <ec:column property="fullName" title="Name"/> <ec:column property="nickName" /> <ec:column property="term" /> </ec:row> </ec:table>
<input type="button" name="sel" class="button" value="List Selected Presidents" onclick="document.forms.presForm.submit();" />
<script type="text/javascript"> function setPresidentState(chkbx) { //make sure that always know the state of the checkbox if (chkbx.checked) { eval('document.forms.presForm.chkbx_' + chkbx.name).value='SELECTED'; } else { eval('document.forms.presForm.chkbx_' + chkbx.name).value='UNSELECTED'; } } </script>
</form>
表標簽form屬性參照最近的form是你使用這個特性所必須知道的,為了更好的理解這個特性,介紹更多的關于內部實現技術的細節是值得的。
如果您不特意指定form屬性,eXtremeTable自動在表附近包上一個form。所有表的動作例如:排序、過濾、分頁將自動給一些隱藏的input元素賦值,然后提交這個form到表標簽action屬性設置的Aciton。 這非常有效,除非您想要將自己的form元素設置到表體,或者想將這個表放到別的form里。
表標簽form屬性參照最近的form,所有表的動作例如:排序、過濾、分頁將自動給一些隱藏的input元素賦值,但是現在 最近form的action屬性將要改變表標簽的動作。這非常重要,因為:當排序、過濾、分頁時,eXtremeTable能夠從一個controller得到數據集合 ,但是提交這個form到別的controller來處理這個form時需要對用戶的輸入進行處理。然而,這些對于你使用表標簽來說都是透明的。 就像你現在做的那樣簡單地設置表標簽的action屬性,然后設置相關的form到你想提交的位置。
示例的第一列是checkbox。因為這列不需要參照bean的屬性,alias屬性用來唯一地標識這列。你可以使用property 屬性,但是alias屬性使這列如何使用更清楚。alias屬性還被用來當同樣的屬性被多列使用時唯一地標識一列。
您也許想知道定制的cell是如何通過名稱selectedPresident被參照的(cell="selectedPresident")。這是一個 對eXtremeTable的preferences特性更強的使用。所有要做的就是在extremecomponents.properties文件中添加一個屬性。 請參考Preferences來了解更多的信息
column.cell.selectedPresident=org.extremesite.cell.SelectedPresidentCell
column.cell.selectedPresident就是你定義的用來參照這個cell的名稱。
當然你也可以使用這個Cell的全名來進行參照。
<ec:column alias="checkbox" title=" " width="5px" filterable="false" sortable="false" viewsAllowed="compact" cell="org.extremesite.cell.SelectedPresidentCell" />
在屬性文件中定義參照更方便,它可以被任何JSP文件引用。如果類名或包名改變的話你只需要對一個地方進行修改。
JavaScript的setPresidentState()方法被定制cell用來設置每個checkbox元素的是否被選中。 設置一個隱藏元素的原因是為了獲得瀏覽器的動作而不提交沒有選中的checkbox。通過這個Controller將一直知道一個元素是否別選中。
定制的cell被用來生成checkbox,另外它也創建一個隱藏元素用來表示這個checkbox元素是否被選中。 當用戶進行排序、過濾、分頁時,被選中的數據集合將被放到session里。
getExportDisplay()方法沒有返回值,因為治理只需要Html顯示。
public class SelectedPresidentCell implements Cell { public String getExportDisplay(TableModel model, Column column) { return null; }
public String getHtmlDisplay(TableModel model, Column column) { HtmlBuilder html = new HtmlBuilder();
CellBuilder.tdStart(html, column);
try { Object bean = model.getCurrentRowBean(); String presidentId = BeanUtils.getProperty(bean, "presidentId");
Collection selectedPresidentsIds = (Collection)model.getContext().getSessionAttribute(SelectedPresidentsConstants.SELECTED_PRESIDENTS); if (selectedPresidentsIds != null && selectedPresidentsIds.contains(presidentId)) { html.input("hidden").name("chkbx_" + presidentId).value(SelectedPresidentsConstants.SELECTED).xclose(); html.input("checkbox").name(BeanUtils.getProperty(bean, "presidentId")); html.onclick("setPresidentState(this)"); html.checked(); html.xclose(); } else { html.input("hidden").name("chkbx_" + presidentId).value(SelectedPresidentsConstants.UNSELECTED).xclose(); html.input("checkbox").name(BeanUtils.getProperty(bean, "presidentId")); html.onclick("setPresidentState(this)"); html.xclose(); } } catch (Exception e) {}
CellBuilder.tdEnd(html);
return html.toString(); } }
提示:Spring框架的Controller和Struts框架的Action非常相像。
當在另外的form中使用eXtremeTable時,你可能有1個或2個controllers。當form被提交時,你需要一個controller 來處理用戶的輸入并重新定向到另外的JSP頁面。當排序、過濾、分頁時,你可能有另外的controller來得到表使用的數據集合并重定向會本頁。或者你可以在同一個controller中分別處理。
checkbox示例里我使用一個controller來關聯表標簽的action屬性。我也使用另外一個controller來關聯form元素的動作。
這個controller負責調用SelectedPresidentsUtils來保存被選中的presidentIds到session里并回到同一頁。
SelectedPresidentsUtils.saveSelectedPresidentsIDs(request); Collection presidents = presidentsService.getPresidents(); request.setAttribute("presidents", presidents);
這個controller負責通過presidentIds得到數據集并重定向到下一個Jsp頁面
Collection selectedPresidentsIds = SelectedPresidentsUtils.saveSelectedPresidentsIDs(request); Collection selectedPresidents = SelectedPresidentsUtils.getSelectedPresidents(presidentsService.getPresidents(), selectedPresidentsIds); request.setAttribute("selected", selectedPresidents); request.getSession().removeAttribute(SelectedPresidentsConstants.SELECTED_PRESIDENTS);
我將列出保存presidentIds到session的代碼。我經常被問到如何重新得到eXtremeTable中form元素的值。 它的原理是設置form輸入元素名字屬性值前面加上一些東西來唯一標識元素
本示例中我關心的是以chkbx開頭參數的元素。chkbx后面是唯一的關聯到checkbox的presidentId。它被用來判斷這個checkbox是否別選中。
public static Collection saveSelectedPresidentsIDs(HttpServletRequest request) { Collection presidents = (Collection) request.getSession().getAttribute(SelectedPresidentsConstants.SELECTED_PRESIDENTS);
if (presidents == null) { presidents = new ArrayList(); request.getSession().setAttribute(SelectedPresidentsConstants.SELECTED_PRESIDENTS, presidents); }
Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String parameterName = (String) parameterNames.nextElement(); if (parameterName.startsWith("chkbx_")) { String presidentId = StringUtils.substringAfter(parameterName, "chkbx_"); String parameterValue = request.getParameter(parameterName); if (parameterValue.equals(SelectedPresidentsConstants.SELECTED)) { if (!presidents.contains(presidentId)) { presidents.add(presidentId); } } else { presidents.remove(presidentId); } } }
return presidents; }
eXtremeTable使用View接口來生成HTML。你可以使用發行包已經提供的視圖,或者你可以插入自己的視圖實現。 現在,創建你自己的視圖相對比較簡單,但討論一些設計想法和如何著手實現一個定制的視圖還是有價值的。
我想使創建定制視圖簡單,但不是想構造一個更復雜的類似swing的模型,原因是那需要創建大量的對象來處理對應的內部工作。 eXtremeTable以高效為目標,我也想在視圖的實現上貫徹這種想法,所以我決定創建一系列的靜態構造器類來實現分解的最小功能。你可以通過組合這些功能來實現你的定制視圖。
學習定制視圖的最好途徑是閱讀已經存在的視圖的源代碼,修改它來滿足你的需求。如果我示范所有東西的話,這篇指南將變的非常冗長。取而代之的是我將直接修改默認視圖的工具條作為定制視圖的一個示例。 對于不同構造器的具體細節我建議你閱讀源代碼。我也將盡量更新javadocs來提供更好的幫助。
實現View接口的類有3次插入內容的機會。beforeBody()方法會被立刻調用,body()方法在每一行的每一列處理的時候調用。 afterBody()方法是被eXtremeTable調用的最后方法,它將返回代表視圖的一個對象。在這個HTML視圖示例里,它將是一個字符串。
public interface View { public void beforeBody(TableModel model); public void body(TableModel model, Column column); public Object afterBody(TableModel model); }
在這篇指南里我將直接修改工具條來實現這網站上Messages示例的定制視圖。
public class MessagesView extends AbstractHtmlView { protected void toolbar(TableModel model) { TwoColumnTableLayout toolbar = new MessagesToolbar(); toolbar.layout(getHtmlBuilder(), model); }
protected void statusBar(TableModel model) { TwoColumnRowLayout statusBar = new MessagesStatusBar(); statusBar.layout(getHtmlBuilder(), model); } }
這里使用的是默認視圖,因此它擴展了虛擬視圖來修改工具條和狀態條。如何修改工具條和(或)狀態條也是開發人員問的最多問題。
默認視圖的工具條位于表的上方包括翻頁鏈接和標題。工具條使用TwoColumnTableLayout,它是一個用于提供在自己表中實現左右兩列布局的虛擬類。 它將實現能夠浮在表上方的完美布局。下面就是你需要關心的虛擬方法,在實際的html視圖中已經為你完成了這個布局。
public abstract class TwoColumnTableLayout { protected abstract boolean showLayout(TableModel model); protected abstract void columnLeft(HtmlBuilder html, TableModel model); protected abstract void columnRight(HtmlBuilder html, TableModel model); }
showLayout()方法用來阻止或導致布局的展現。在我的定制視圖中如果翻頁或(和)導出顯示那么工具條將展現。
protected boolean showLayout(TableModel model) { boolean showPagination = BuilderUtils.showPagination(model); boolean showExports = BuilderUtils.showExports(model); if (!showPagination && !showExports) { return false; }
return true; }
下面顯示了左列和右列的部分代碼。注意在我的定制視圖中首頁和前一頁使用了文字來替代圖片顯示。我真正希望示范的是你需要做的:找到正確的構造器類并且僅僅是擴展HtmlBuilder的標簽。 構造器類對于示范如何找到模型里的信息(以便你能夠做比他們能夠提供的更多的定制工作)也非常有用,。
protected void columnLeft(HtmlBuilder html, TableModel model) { html.td(2).close(); TableBuilder.title(html, model); html.tdEnd(); }
protected void columnRight(HtmlBuilder html, TableModel model) { boolean showPagination = BuilderUtils.showPagination(model); ... if (showPagination) { html.td(4).close(); ToolbarBuilder.firstPageItemAsText(html, model); html.tdEnd();
html.td(4).close(); ToolbarBuilder.prevPageItemAsText(html, model); html.tdEnd(); ... } ... }
為了使用這個視圖你需要在Preferences定義一個別名。 你可以省略這部而在JSP直接給出這個視圖的完整有效的類名,不過Preferences更為簡潔。
table.view.messages=org.extremesite.view.MessagesView
TableTag也將設置視圖屬性來使用MessagesView視圖。
<ec:table view="messages">
如果不清楚Preferences和TableTag定義語法請參考Preferences指南。
攔截特性被用在運行時需要修改屬性值的時候,它使得改變基于數據的eXtremeTable的行為成為可能。在閱讀擴展標簽屬性時,你會發現它和擴展標簽屬性具有同樣的概念和方法標識。 區分使用他們的首要準則是:如果需要向TLD里已經定義的并且能夠在JSP中訪問的標簽添加新的屬性時,應該使用擴展標簽屬性;如果僅僅是需要修改已經定義好的屬性的值的時候,應該使用攔截器。
你可能需要了解更多的eXtremeTable如何運作的技術背景才能完全理解這種特性。 eXtremeTable首先做的就是遍歷所有標簽并創建對應的模型beans (pojos)。beans是具有和標簽一樣屬性,但是使用真實類型來替換僅僅使用字符串類型的對象。beans是被模型使用并且是你需要使用攔截特性修改的對象。 所有的攔截器接口都定義了一個add方法, add方法被用來處理模型bean第一次創建時的屬性。行和列的攔截器還有一個modify 方法。modify方法可以在當行和類進行處理是對屬性值進行操作。
下面列出了具有攔截特性的標簽和他們需要被實現的接口,Bean欄顯示了被模型創建的Bean。
示范攔截特性的完美示例就是根據一定的標準來對行進行高亮顯示,這也是我們將要完成的示例。它很短也很簡單,不過它實現的概念同樣適用于每一個攔截器接口。
我們需要做的第一件事就是實現InterceptRow接口。你會注意到這個接口有兩個方法:addRowAttributes() 和modifyRowAttributes()。addRowAttributes方法在行bean創建的時候被調用, modifyRowAttributes方法在表處理當前頁面行的時候被調用。
public class MarkerIntercept implements InterceptRow { public void addRowAttributes(TableModel tableModel, Row row) { }
public void modifyRowAttributes(TableModel model, Row row) { President president = (President) model.getCurrentRowBean(); String career = president.getCareer(); if (StringUtils.contains(career, "Soldier")) { row.setStyle("background-color:#fdffc0;"); } else { row.setStyle(""); } } }
在Preferences里你應該定義這個行攔截器的別名。
row.intercept.marker=org.extremesite.intercept.MarkerIntercept
這樣就可以在行標簽中使用攔截器MarkerIntercept了。
<ec:row intercept="marker">
如果不清楚Preferences和TableTag定義語法請參考Preferences指南。
在你需要處理大量數據時你應該考慮使用eXtremeTable的Limit特性。Limit這個名字來自MySQL的limit 命令,Limit接口的目的就是如何對表的結果集進行limit處理。Limit實現知道當排序、過濾、分頁、導出時,用戶如何與表互相作用。有了這些信息你 將能夠使用可能是最有效的方式顯示正確的過濾、排序后的請求頁面。
為了示范Limit特性,我將要做的工作將分解為JSP、Controller、Service和DAO。這示范了一種使用分層的方式來處理 Limit。你可以根據自己的需要來增加或減少層。本示例也使用了Spring框架來重新得到使用Spring的JDBC取得的數據,因此你的代碼看起來可能有點不同。eXtremeTable的一個特點就是不依賴任何框架和容器。
為了使用Limit特性,eXtremeTable需要使用limit特定的RetrieveRowsCallback、 FilterRowsCallback和SortRowsCallback接口。eXtremeComponents提供了每個接口的一個實現,可以簡單地通過設置每個屬性值為limit來簡單來使用。
<ec:table items="presidents" retrieveRowsCallback="limit" filterRowsCallback="limit" sortRowsCallback="limit" view="limit" > ...
另外視圖屬性參照一個名為limit的定制視圖。這是一個簡單修改默認eXtremeTable視圖,不包含最后頁工具條的實現。這僅僅關系到你是否能取得確切需要的行。 一些數據庫例如Oracle和MySQL都提供了一種得到確定行的特性,但是,其他的數據庫例如:Sybase沒有提供特性。在我的示例中我考慮最壞的情況你的數據庫不支持這種特性。
即使你的數據庫不提供取得特定行的特性,當你考慮用戶如何和表協同工作時,Limit仍然非常有意義。用戶通常會對一些數據進行排序、過濾和分頁。 這個例子中15條數據構成一頁,第一頁需要15條數據,第二頁需要30條數據,第三頁需要45條數據,以此類推。在經過一段時間分頁后,他們常常使用過濾來提煉數據。 即使他們不這樣做,他們也必須在此之前對大量的數據進行分頁,這將影響效率。當然如果允許用戶點擊最后頁,那么所有的數據都將被取出,這將非常影響效率。
提示:Spring框架的Controller和Struts框架的Action非常相像。
controller首先需要創建一個Limit。為了完成這個你需要得到一些關于Context和LimitFactory的幫助。
Context context = new HttpServletRequestContext(request); LimitFactory limitFactory = new TableLimitFactory(context); Limit limit = new TableLimit(limitFactory);
Context是一個處理取得屬性的接口,LimitFactory使用Context來找出用戶如何和eXtremeTable交互。 然后Limit使用LimitFactory來組裝自己。
為了初始化Limit,它將包含所有的有用的信息。這些信息包括數據將被如何排序和過濾,哪一頁將被顯示和是否允許被導出。
然而,Limit仍然需要得到行的信息,這樣正確的信息頁面才能被顯示給用戶。行信息包括開始行、結束行、當前顯示行。 controller必須從service得到這些信息,而Service將從dao中得到這些信息。這里我只給出Controller端的代碼。
int totalRows = presidentsService.getTotalPresidents(limit.getFilterSet(), limit.isExported()); limit.setRowAttributes(totalRows, defaultRowsDisplayed);
limit需要得到所有的行來得到行的信息。service需要知道那些被過濾,不管這些數據是否要導出。為了設置行信息,默認的一頁顯示的行數需要被設置。 這可以通過對TableTag的rowsDisplayed屬性設置一個確定的數值來實現。
現在我們只需要從services得到Collection數據。
Collection presidents = presidentsService.getPresidents(limit.getFilterSet(), limit.getSort(), limit.getRowEnd());
因為limit已經包含所有信息,這將十分容易。所有需要做的就是傳入過濾器,排序和最后行的信息。 最后要做的是將Collections和totalRow這些信息傳送回JSP以便eXtremeTable知道如何顯示這些信息。
request.setAttribute("presidents", presidents); request.setAttribute("totalRows", new Integer(totalRows));
service需要和dao進行交互來得到總行數和Collection。
controller需要到第一條信息就是總行數。
public int getTotalPresidents(FilterSet filterSet, boolean isExported) { String totalQuery = presidentsDao.getTotalPresidentsQuery(); String modTotalQuery = filterQuery(filterSet, totalQuery); int totalRows = presidentsDao.getTotalPresidents(modTotalQuery); if (isExported && totalRows > maxExportRows) { totalRows = maxExportRows; } return totalRows; }
service和dao一起來過濾結果集,它的工作方式是在Where語句后面增加更多的AND語句來修改查詢字符串。為此,你需要和Limit FilterSet一起工作。
FilterSet是一個過濾器對象數組,一個過濾器包括一個bean property和這個過濾器的值。或者,簡單的說就是用戶想要過濾的行和他們輸入的值。這使得它非常容易交互。service只需要迭代所有的 FilterSet并調用dao來拼接查詢語句。(譯者注:過濾的實現方式是:在Where后面增加And語句來改變查詢語句以達到對數據進行過濾的效果)
private String filterQuery(FilterSet filterSet, String query) { if (!filterSet.isFiltered() || filterSet.isCleared()) { return query; }
Filter filters[] = filterSet.getFilters(); for (int i = 0; i < filters.length; i++) { Filter filter = filters[i]; String property = filter.getProperty(); String value = filter.getValue(); query = presidentsDao.filterQuery(query, property, value); }
return query; }
query修改包括了filter信息,總行數。在一些情況下這就足夠,但是當用戶導出數據時仍然存在一個潛在的問題。為了保持高效 service不允許導出超出一個最大行數的數據。
controller需要到第二條信息就是Collection。
public Collection getPresidents(FilterSet filterSet, Sort sort, int rowEnd) { String patientsQuery = presidentsDao.getPresidentsQuery(); String modPatientsQuery = filterQuery(filterSet, patientsQuery); modPatientsQuery = sortQuery(sort, modPatientsQuery); modPatientsQuery = presidentsDao.limitQuery(rowEnd, modPatientsQuery); return presidentsDao.getPresidents(modPatientsQuery); }
和前面一樣,service和dao一起來過濾結果集。
另外query字符串需要擴展ORDER BY語句以便數據按照正確的方式進行排序。Sort包含一個bean property和 sortOrder值(正序還是逆序)。service僅僅需要使用Sort來調用dao。
private String sortQuery(Sort sort, String query) { if (!sort.isSorted()) { String defaultSortOrder = presidentsDao.getDefaultSortOrder(); if (StringUtils.isNotBlank(defaultSortOrder)) { return query + defaultSortOrder; }
return query; }
String property = sort.getProperty(); String sortOrder = sort.getSortOrder();
return presidentsDao.sortQuery(query, property, sortOrder); }
query字符串最后需要的修改就是增加數據庫特別的指令來limit將要被返回的結果集。這就是limitQuery() 方法的作用。
dao為service負責底層數據工作。
為了真正理解dao,query字符串需要被展示。
這就是得到數據的presidents query字符串:
private final static String presidentsQuery = " SELECT " + " president_id presidentId, " + " first_name firstName, " + " last_name lastName, " + " nick_name nickName, " + " concat(first_name, ' ',last_name) fullName, " + " term, " + " born, " + " died, " + " education, " + " career, " + " political_party politicalParty " + " FROM presidents ";
這是得到總行數的query字符串:
private final static String totalPresidentsQuery = " SELECT count(*) FROM presidents ";
1.4.2.?Filter 和 Sort Query 字符串
兩個最有趣的方法就是過濾和排序。
filter看起來像這樣:
public String filterQuery(String query, String property, String value) { StringBuffer result = new StringBuffer(query);
if (query.indexOf("WHERE") == -1) { result.append(" WHERE 1 = 1 "); //stub WHERE clause so can just append AND clause }
if (property.equals("fullName")) { result.append(" AND concat(first_name, ' ',last_name) like '%" + value + "%'"); } else if (property.equals("nickName")) { result.append(" AND nick_name like '%" + value + "%'"); } else { result.append(" AND " + property + " like '%" + value + "%'"); }
return result.toString(); }
filterQuery()方法需要增加正確的AND語句到query字符串。
sort看起來非常類似:
public String sortQuery(String query, String property, String sortOrder) { StringBuffer result = new StringBuffer(query + " ORDER BY ");
if (property.equals("fullName")) { result.append("concat(first_name, ' ',last_name) " + sortOrder); } else { result.append(property + " " + sortOrder); }
return result.toString(); }
sortQuery()方法需要增加正確的ORDER BY語句到query字符串。
1.4.3.?Limit Query String
現在query字符串修改能夠正確的進行filter和sort,它還需要修改以便只取頁面顯示相關的數據。MySQL為s the limit命令。
public String limitQuery(int rowEnd, String query) { return query + " limit " + rowEnd; }
service需要的唯一東西就是:總行數和Collection。
public Collection getPresidents(final String query) { return jdbcTemplate.query(query, new ResultReader() { List results = new ArrayList(); public List getResults() { return results; }
public void processRow(ResultSet rs) throws SQLException { President president = new President(); president.setPresidentId(new Integer(rs.getInt("presidentId"))); president.setFirstName(rs.getString("firstName")); president.setLastName(rs.getString("lastName")); president.setNickName(rs.getString("nickName")); president.setFullName(rs.getString("fullName")); president.setTerm(rs.getString("term")); president.setBorn(rs.getDate("born")); president.setDied(rs.getDate("died")); president.setEducation(rs.getString("education")); president.setCareer(rs.getString("career")); president.setPoliticalParty(rs.getString("politicalParty")); results.add(president); } }); }
public int getTotalPresidents(final String query) { return jdbcTemplate.queryForInt(query); }
ResultReader是一個幫助處理JDBC查詢的Spring特殊類,作為一個callback來處理JDBC ResultSet。jdbcTemplate是對JDBC連接的抽象。
最后,這是service需要的默認sort順序:
public String getDefaultSortOrder() { return " ORDER BY concat(first_name, ' ', last_name) "; }
為了設置全局屬性和設置,你需要使用Preferences特性,它現在使用一個屬性文件來實現。本文檔將很好地介紹如何在web.xml里設置Preferences, 以及一些需要被定義的通用屬性。在這里我非常樂意介紹一些關于Preferences的進一步用法。
所有標簽屬性表示一個可插接的接口,它可以通過給出實現的全路徑來設置。這為插接實現提供了一條便利的途徑。當然這存在一些為過長術語的設計和維護的考慮。 第一,對你的接口實現進行硬編碼;第二,如果你需要在別的JSP中用到同一個接口實現,你需要拷貝你全路徑。解決這兩個問題的有效辦法就是在Preferences中聲明一切。
下面列出的是可以在Preferences中申明的所有接口。Tag列展示的是eXtremeTable的標簽,Attribute 列展示的是相關標簽的對應屬性。Interface列展示的是需要被實現的Java接口。Preference Key列展示的是 Preferences里對應的健。
提示:當在寫作本指南的時候,我意識到我忘記了讓標簽ColumnsTag的autoGenerateColumns 屬性和Preferences協同工作。這將在下一版修正。
上表展示了如何聲明preference鍵,但是沒有解釋如何指定有意義的別名。如果你注意到preference鍵提供了一致的語法 tag.attribute,指定鍵的別名僅僅是在它的基礎上進行擴展。它的語法為: tag.attribute.alias。
eXtremeTable提供了一個名為RowCountCell定制的cell,它的作用是現實當前的行數。我將在Preferences里使用ColumnTag cell聲明來示范RowCountCell的使用。
首先通過實現Cell接口或者擴展AbstractCell來編寫具體的實現類。
public class RowCountCell extends AbstractCell { protected String getCellValue(TableModel model, Column column) { int rowcount = ((model.getLimit().getPage() - 1) * model.getLimit().getCurrentRowsDisplayed()) + model.getRowHandler().getRow().getRowCount(); return String.valueOf(rowcount); } }
然后在Preferences (屬性文件)進行聲明并給出別名。eXtremeTable在一個Preferences里保存所有的配置信息,你可以通過使用本地 Preferences的來覆蓋任何的這些屬性。
RowCountCell默認的別名是rowCount:
column.cell.rowCount=org.extremecomponents.table.cell.RowCountCell
在ColumnTag中通過別名引用Cell:
<ec:column alias="count" cell="rowCount"/>
現在你可以通過rowCount來引用這個Cell,如果包名改變了你只需要對Preferences進行修改。
提示:本示例中我使用了ColumnTag的別名屬性。別名屬性應用在有兩列使用同樣的property,也應用在列不直接和列的 bean property關聯的情況下。本示例就屬于這種情況。
Lyyb2001
|