|
Posted on 2008-03-04 22:19 skycity 閱讀(599) 評(píng)論(0) 編輯 收藏 所屬分類: J2EE技術(shù)
列的filterCell屬性控制過濾器如何顯示,它和cell屬性非常相像并且也是實(shí)現(xiàn)Cell接口。馬上要定義的是默認(rèn)的和droplist這兩個(gè)過濾器cells。 默認(rèn)的是一個(gè)輸入框元素而droplist是一個(gè)下拉列表元素。當(dāng)然,如果你需要進(jìn)行一些定制你可以插接自己的實(shí)現(xiàn)。
最近,我被問到是否能夠?qū)崿F(xiàn)一個(gè)過濾器cell,顯示已經(jīng)通過別的過濾器過濾得到數(shù)據(jù)子集。答案當(dāng)然是肯定的,而且這是我將在這里示范的。通常定制的 cell可以很容易被創(chuàng)建,這個(gè)示例將印證這點(diǎn)。在這個(gè)示例里last name列里顯示的將是通過first name過濾后的子集。如果沒有通過 first name過濾那么所有值都將被顯示。
通常你只需要為過濾器cell實(shí)現(xiàn)Cell接口。然而,因?yàn)槲覀円獎(jiǎng)?chuàng)建的過濾器cell是一個(gè)下拉列表,我們可以通過擴(kuò)展 FilterDroplistCell來獲得它已經(jīng)提供的很多功能,F(xiàn)ilterDroplistCell是發(fā)行包已經(jīng)提供的cells之一。
我們需要覆蓋FilterDroplistCell的唯一方法是getFilterDropList()。這是整個(gè)類的全部代碼:
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; } }
如果你比較這個(gè)類和父類,你會(huì)發(fā)現(xiàn)它們只有微小的區(qū)別。
首先需要注意的是我們需要找出first name是否已經(jīng)被過濾了。
String firstNameFilter = model.getLimit().getFilterSet().getValue("firstName");
然后我們需要判斷當(dāng)前bean的first name值是否和first name過濾器值相同。如果相同,將當(dāng)前的last name值 添加到droplist中。
String firstName = BeanUtils.getProperty(bean, "firstName"); if (StringUtils.isNotBlank(firstNameFilter) && !firstName.equals(firstNameFilter)) { continue; }
如果last name將添加到droplist中,我們需要檢查droplist中是否已經(jīng)包含了這個(gè)值。如果沒有,我們就把它添加到droplist中。
String lastName = BeanUtils.getProperty(bean, column.getProperty()); if ((lastName != null) && !droplist.contains(lastName)) { droplist.add(lastName); }
為了使用這個(gè)Cell你應(yīng)該在Preferences中聲明一個(gè)別名。 當(dāng)然,你可以省略這步而在JSP中提供這個(gè)Cell實(shí)現(xiàn)類的全路徑,但是使用Preferences更簡(jiǎn)潔。
column.filterCell.filteredDroplist=org.extremesite.cell.FilteredDroplistCell
在ColumnTag通過設(shè)置filterCell屬性來使用FilteredDroplistCell。
<ec:column property="lastName" filterCell="filteredDroplist"/>
如果不清楚Preferences和ColumnTag定義語法請(qǐng)參考Preferences指南。
FilterRowsCallback被用來過濾傳給eXtremeTable的Beans的Collection。 FilterRowsCallback的默認(rèn)實(shí)現(xiàn)是得到Beans或Maps的Collection,然后通過實(shí)現(xiàn)jakarta Predicate接口來進(jìn)行過濾。當(dāng)然,如果你需要進(jìn)行一些定制你可以插接自己的實(shí)現(xiàn)。
首先聲明,本示例代碼包含一些從原包中剪切、粘貼的代碼(雖然不是很多)。在 最初的最終發(fā)行包之后,值過濾得到進(jìn)一步改善使得更具復(fù)用性并更容易實(shí)現(xiàn),可能和定制cell代碼行數(shù)相同。 當(dāng)然,我被要求并非常樂意示范如何在當(dāng)前代碼基礎(chǔ)上實(shí)現(xiàn)定制過濾。這有非常清晰的hooks實(shí)現(xiàn),并且很容易實(shí)現(xiàn)。
本示例示范了如何調(diào)整代碼為過濾器提供一個(gè)精確的比較功能。當(dāng)前的實(shí)現(xiàn)是通過使用StringUtils.contains()方法進(jìn)行模糊比較。 本示例將使用StringUtils.equals()方法。你可以按照你的需要來調(diào)整代碼進(jìn)行更多定制。
1.1.?定制FilterRowsCallback示例
首先你需要做的是創(chuàng)建一個(gè)實(shí)現(xiàn)Predicate接口的定制類。Predicate要求我們實(shí)現(xiàn)evaluate()方法來判斷是否包含 當(dāng)前bean。因?yàn)槟銉H僅調(diào)整現(xiàn)在已有的功能,首先得到filterPredicate的源代碼(在發(fā)行包的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; }
...
} }
然后我們需要實(shí)現(xiàn)和Predicate共同作用的FilterRowsCallback接口。再一次從發(fā)行包的callback包下拷貝ProcessRowsCallback源代碼到你的工程里。 請(qǐng)參照我們創(chuàng)建的定制的ExactMatchFilterPredicate 類來確認(rèn)僅僅實(shí)現(xiàn)了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; }
...
} }
為了使用這個(gè)FilterRowsCallback你應(yīng)該在Preferences中聲明一個(gè)別名。當(dāng)然,你可以省略這步而在JSP中提供這個(gè)FilterRowsCallback實(shí)現(xiàn)類的全路徑,但是使用Preferences更簡(jiǎn)潔。
table.filterRowsCallback.exactMatch=org.extremesite.callback.ExactMatchFilterRows
在TableTag通過設(shè)置filterRowsCallback屬性來使用ExactMatchFilterRows。
<ec:table filterRowsCallback="exactMatch"/>
如果不清楚Preferences和ColumnTag定義語法請(qǐng)參考Preferences指南。
eXtremeTable本質(zhì)上是一個(gè)form組件,所以我假定表被包在form里,所有的功能都被認(rèn)為是對(duì)form元素的操作。如果你想在表體中包含一些定制的form元素, 或者想將eXtremeTable嵌入到另外的form中,那么你就要使用表標(biāo)簽的form屬性用來參照最近的form。
為了示范form特性,我們要做的工作將分解為JSP,Cell和Controller。
下面列出的是checkbox示例的完整代碼。想要強(qiáng)調(diào)的主要事情是表標(biāo)簽form屬性設(shè)置為presForm,它參照被稱為presForm的form元素。
同時(shí)請(qǐng)注意表標(biāo)簽的autoIncludeParameters屬性。進(jìn)行排序、過濾、分頁(yè)時(shí),默認(rèn)的eXtremeTable將保持所有傳至JSP頁(yè)面的參數(shù)。 這個(gè)特性對(duì)于內(nèi)部其他的form進(jìn)行排序、過濾、分頁(yè)時(shí),用于高效復(fù)制form元素同樣有效。可以設(shè)置 autoIncludeParameters屬性為false來固定它。
在這個(gè)form使用id屬性是因?yàn)閤htm標(biāo)準(zhǔn)的要求,同時(shí)你也可以使用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>
表標(biāo)簽form屬性參照最近的form是你使用這個(gè)特性所必須知道的,為了更好的理解這個(gè)特性,介紹更多的關(guān)于內(nèi)部實(shí)現(xiàn)技術(shù)的細(xì)節(jié)是值得的。
如果您不特意指定form屬性,eXtremeTable自動(dòng)在表附近包上一個(gè)form。所有表的動(dòng)作例如:排序、過濾、分頁(yè)將自動(dòng)給一些隱藏的input元素賦值,然后提交這個(gè)form到表標(biāo)簽action屬性設(shè)置的Aciton。 這非常有效,除非您想要將自己的form元素設(shè)置到表體,或者想將這個(gè)表放到別的form里。
表標(biāo)簽form屬性參照最近的form,所有表的動(dòng)作例如:排序、過濾、分頁(yè)將自動(dòng)給一些隱藏的input元素賦值,但是現(xiàn)在 最近form的action屬性將要改變表標(biāo)簽的動(dòng)作。這非常重要,因?yàn)椋寒?dāng)排序、過濾、分頁(yè)時(shí),eXtremeTable能夠從一個(gè)controller得到數(shù)據(jù)集合 ,但是提交這個(gè)form到別的controller來處理這個(gè)form時(shí)需要對(duì)用戶的輸入進(jìn)行處理。然而,這些對(duì)于你使用表標(biāo)簽來說都是透明的。 就像你現(xiàn)在做的那樣簡(jiǎn)單地設(shè)置表標(biāo)簽的action屬性,然后設(shè)置相關(guān)的form到你想提交的位置。
示例的第一列是checkbox。因?yàn)檫@列不需要參照bean的屬性,alias屬性用來唯一地標(biāo)識(shí)這列。你可以使用property 屬性,但是alias屬性使這列如何使用更清楚。alias屬性還被用來當(dāng)同樣的屬性被多列使用時(shí)唯一地標(biāo)識(shí)一列。
您也許想知道定制的cell是如何通過名稱selectedPresident被參照的(cell="selectedPresident")。這是一個(gè) 對(duì)eXtremeTable的preferences特性更強(qiáng)的使用。所有要做的就是在extremecomponents.properties文件中添加一個(gè)屬性。 請(qǐng)參考Preferences來了解更多的信息
column.cell.selectedPresident=org.extremesite.cell.SelectedPresidentCell
column.cell.selectedPresident就是你定義的用來參照這個(gè)cell的名稱。
當(dāng)然你也可以使用這個(gè)Cell的全名來進(jìn)行參照。
<ec:column alias="checkbox" title=" " width="5px" filterable="false" sortable="false" viewsAllowed="compact" cell="org.extremesite.cell.SelectedPresidentCell" />
在屬性文件中定義參照更方便,它可以被任何JSP文件引用。如果類名或包名改變的話你只需要對(duì)一個(gè)地方進(jìn)行修改。
JavaScript的setPresidentState()方法被定制cell用來設(shè)置每個(gè)checkbox元素的是否被選中。 設(shè)置一個(gè)隱藏元素的原因是為了獲得瀏覽器的動(dòng)作而不提交沒有選中的checkbox。通過這個(gè)Controller將一直知道一個(gè)元素是否別選中。
定制的cell被用來生成checkbox,另外它也創(chuàng)建一個(gè)隱藏元素用來表示這個(gè)checkbox元素是否被選中。 當(dāng)用戶進(jìn)行排序、過濾、分頁(yè)時(shí),被選中的數(shù)據(jù)集合將被放到session里。
getExportDisplay()方法沒有返回值,因?yàn)橹卫碇恍枰狧tml顯示。
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非常相像。
當(dāng)在另外的form中使用eXtremeTable時(shí),你可能有1個(gè)或2個(gè)controllers。當(dāng)form被提交時(shí),你需要一個(gè)controller 來處理用戶的輸入并重新定向到另外的JSP頁(yè)面。當(dāng)排序、過濾、分頁(yè)時(shí),你可能有另外的controller來得到表使用的數(shù)據(jù)集合并重定向會(huì)本頁(yè)。或者你可以在同一個(gè)controller中分別處理。
checkbox示例里我使用一個(gè)controller來關(guān)聯(lián)表標(biāo)簽的action屬性。我也使用另外一個(gè)controller來關(guān)聯(lián)form元素的動(dòng)作。
1.3.1.?表標(biāo)簽動(dòng)作Controller
這個(gè)controller負(fù)責(zé)調(diào)用SelectedPresidentsUtils來保存被選中的presidentIds到session里并回到同一頁(yè)。
SelectedPresidentsUtils.saveSelectedPresidentsIDs(request); Collection presidents = presidentsService.getPresidents(); request.setAttribute("presidents", presidents);
1.3.2.?Form動(dòng)作Controller
這個(gè)controller負(fù)責(zé)通過presidentIds得到數(shù)據(jù)集并重定向到下一個(gè)Jsp頁(yè)面
Collection selectedPresidentsIds = SelectedPresidentsUtils.saveSelectedPresidentsIDs(request); Collection selectedPresidents = SelectedPresidentsUtils.getSelectedPresidents(presidentsService.getPresidents(), selectedPresidentsIds); request.setAttribute("selected", selectedPresidents); request.getSession().removeAttribute(SelectedPresidentsConstants.SELECTED_PRESIDENTS);
我將列出保存presidentIds到session的代碼。我經(jīng)常被問到如何重新得到eXtremeTable中form元素的值。 它的原理是設(shè)置form輸入元素名字屬性值前面加上一些東西來唯一標(biāo)識(shí)元素
本示例中我關(guān)心的是以chkbx開頭參數(shù)的元素。chkbx后面是唯一的關(guān)聯(lián)到checkbox的presidentId。它被用來判斷這個(gè)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。你可以使用發(fā)行包已經(jīng)提供的視圖,或者你可以插入自己的視圖實(shí)現(xiàn)。 現(xiàn)在,創(chuàng)建你自己的視圖相對(duì)比較簡(jiǎn)單,但討論一些設(shè)計(jì)想法和如何著手實(shí)現(xiàn)一個(gè)定制的視圖還是有價(jià)值的。
我想使創(chuàng)建定制視圖簡(jiǎn)單,但不是想構(gòu)造一個(gè)更復(fù)雜的類似swing的模型,原因是那需要?jiǎng)?chuàng)建大量的對(duì)象來處理對(duì)應(yīng)的內(nèi)部工作。 eXtremeTable以高效為目標(biāo),我也想在視圖的實(shí)現(xiàn)上貫徹這種想法,所以我決定創(chuàng)建一系列的靜態(tài)構(gòu)造器類來實(shí)現(xiàn)分解的最小功能。你可以通過組合這些功能來實(shí)現(xiàn)你的定制視圖。
學(xué)習(xí)定制視圖的最好途徑是閱讀已經(jīng)存在的視圖的源代碼,修改它來滿足你的需求。如果我示范所有東西的話,這篇指南將變的非常冗長(zhǎng)。取而代之的是我將直接修改默認(rèn)視圖的工具條作為定制視圖的一個(gè)示例。 對(duì)于不同構(gòu)造器的具體細(xì)節(jié)我建議你閱讀源代碼。我也將盡量更新javadocs來提供更好的幫助。
實(shí)現(xiàn)View接口的類有3次插入內(nèi)容的機(jī)會(huì)。beforeBody()方法會(huì)被立刻調(diào)用,body()方法在每一行的每一列處理的時(shí)候調(diào)用。 afterBody()方法是被eXtremeTable調(diào)用的最后方法,它將返回代表視圖的一個(gè)對(duì)象。在這個(gè)HTML視圖示例里,它將是一個(gè)字符串。
public interface View { public void beforeBody(TableModel model); public void body(TableModel model, Column column); public Object afterBody(TableModel model); }
在這篇指南里我將直接修改工具條來實(shí)現(xiàn)這網(wǎng)站上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); } }
這里使用的是默認(rèn)視圖,因此它擴(kuò)展了虛擬視圖來修改工具條和狀態(tài)條。如何修改工具條和(或)狀態(tài)條也是開發(fā)人員問的最多問題。
默認(rèn)視圖的工具條位于表的上方包括翻頁(yè)鏈接和標(biāo)題。工具條使用TwoColumnTableLayout,它是一個(gè)用于提供在自己表中實(shí)現(xiàn)左右兩列布局的虛擬類。 它將實(shí)現(xiàn)能夠浮在表上方的完美布局。下面就是你需要關(guān)心的虛擬方法,在實(shí)際的html視圖中已經(jīng)為你完成了這個(gè)布局。
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()方法用來阻止或?qū)е虏季值恼宫F(xiàn)。在我的定制視圖中如果翻頁(yè)或(和)導(dǎo)出顯示那么工具條將展現(xiàn)。
protected boolean showLayout(TableModel model) { boolean showPagination = BuilderUtils.showPagination(model); boolean showExports = BuilderUtils.showExports(model); if (!showPagination && !showExports) { return false; }
return true; }
下面顯示了左列和右列的部分代碼。注意在我的定制視圖中首頁(yè)和前一頁(yè)使用了文字來替代圖片顯示。我真正希望示范的是你需要做的:找到正確的構(gòu)造器類并且僅僅是擴(kuò)展HtmlBuilder的標(biāo)簽。 構(gòu)造器類對(duì)于示范如何找到模型里的信息(以便你能夠做比他們能夠提供的更多的定制工作)也非常有用,。
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(); ... } ... }
為了使用這個(gè)視圖你需要在Preferences定義一個(gè)別名。 你可以省略這部而在JSP直接給出這個(gè)視圖的完整有效的類名,不過Preferences更為簡(jiǎn)潔。
table.view.messages=org.extremesite.view.MessagesView
TableTag也將設(shè)置視圖屬性來使用MessagesView視圖。
<ec:table view="messages">
如果不清楚Preferences和TableTag定義語法請(qǐng)參考Preferences指南。
攔截特性被用在運(yùn)行時(shí)需要修改屬性值的時(shí)候,它使得改變基于數(shù)據(jù)的eXtremeTable的行為成為可能。在閱讀擴(kuò)展標(biāo)簽屬性時(shí),你會(huì)發(fā)現(xiàn)它和擴(kuò)展標(biāo)簽屬性具有同樣的概念和方法標(biāo)識(shí)。 區(qū)分使用他們的首要準(zhǔn)則是:如果需要向TLD里已經(jīng)定義的并且能夠在JSP中訪問的標(biāo)簽添加新的屬性時(shí),應(yīng)該使用擴(kuò)展標(biāo)簽屬性;如果僅僅是需要修改已經(jīng)定義好的屬性的值的時(shí)候,應(yīng)該使用攔截器。
你可能需要了解更多的eXtremeTable如何運(yùn)作的技術(shù)背景才能完全理解這種特性。 eXtremeTable首先做的就是遍歷所有標(biāo)簽并創(chuàng)建對(duì)應(yīng)的模型beans (pojos)。beans是具有和標(biāo)簽一樣屬性,但是使用真實(shí)類型來替換僅僅使用字符串類型的對(duì)象。beans是被模型使用并且是你需要使用攔截特性修改的對(duì)象。 所有的攔截器接口都定義了一個(gè)add方法, add方法被用來處理模型bean第一次創(chuàng)建時(shí)的屬性。行和列的攔截器還有一個(gè)modify 方法。modify方法可以在當(dāng)行和類進(jìn)行處理是對(duì)屬性值進(jìn)行操作。
下面列出了具有攔截特性的標(biāo)簽和他們需要被實(shí)現(xiàn)的接口,Bean欄顯示了被模型創(chuàng)建的Bean。
示范攔截特性的完美示例就是根據(jù)一定的標(biāo)準(zhǔn)來對(duì)行進(jìn)行高亮顯示,這也是我們將要完成的示例。它很短也很簡(jiǎn)單,不過它實(shí)現(xiàn)的概念同樣適用于每一個(gè)攔截器接口。
我們需要做的第一件事就是實(shí)現(xiàn)InterceptRow接口。你會(huì)注意到這個(gè)接口有兩個(gè)方法:addRowAttributes() 和modifyRowAttributes()。addRowAttributes方法在行bean創(chuàng)建的時(shí)候被調(diào)用, modifyRowAttributes方法在表處理當(dāng)前頁(yè)面行的時(shí)候被調(diào)用。
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里你應(yīng)該定義這個(gè)行攔截器的別名。
row.intercept.marker=org.extremesite.intercept.MarkerIntercept
這樣就可以在行標(biāo)簽中使用攔截器MarkerIntercept了。
<ec:row intercept="marker">
如果不清楚Preferences和TableTag定義語法請(qǐng)參考Preferences指南。
在你需要處理大量數(shù)據(jù)時(shí)你應(yīng)該考慮使用eXtremeTable的Limit特性。Limit這個(gè)名字來自MySQL的limit 命令,Limit接口的目的就是如何對(duì)表的結(jié)果集進(jìn)行l(wèi)imit處理。Limit實(shí)現(xiàn)知道當(dāng)排序、過濾、分頁(yè)、導(dǎo)出時(shí),用戶如何與表互相作用。有了這些信息你 將能夠使用可能是最有效的方式顯示正確的過濾、排序后的請(qǐng)求頁(yè)面。
為了示范Limit特性,我將要做的工作將分解為JSP、Controller、Service和DAO。這示范了一種使用分層的方式來處理 Limit。你可以根據(jù)自己的需要來增加或減少層。本示例也使用了Spring框架來重新得到使用Spring的JDBC取得的數(shù)據(jù),因此你的代碼看起來可能有點(diǎn)不同。eXtremeTable的一個(gè)特點(diǎn)就是不依賴任何框架和容器。
為了使用Limit特性,eXtremeTable需要使用limit特定的RetrieveRowsCallback、 FilterRowsCallback和SortRowsCallback接口。eXtremeComponents提供了每個(gè)接口的一個(gè)實(shí)現(xiàn),可以簡(jiǎn)單地通過設(shè)置每個(gè)屬性值為limit來簡(jiǎn)單來使用。
<ec:table items="presidents" retrieveRowsCallback="limit" filterRowsCallback="limit" sortRowsCallback="limit" view="limit" > ...
另外視圖屬性參照一個(gè)名為limit的定制視圖。這是一個(gè)簡(jiǎn)單修改默認(rèn)eXtremeTable視圖,不包含最后頁(yè)工具條的實(shí)現(xiàn)。這僅僅關(guān)系到你是否能取得確切需要的行。 一些數(shù)據(jù)庫(kù)例如Oracle和MySQL都提供了一種得到確定行的特性,但是,其他的數(shù)據(jù)庫(kù)例如:Sybase沒有提供特性。在我的示例中我考慮最壞的情況你的數(shù)據(jù)庫(kù)不支持這種特性。
即使你的數(shù)據(jù)庫(kù)不提供取得特定行的特性,當(dāng)你考慮用戶如何和表協(xié)同工作時(shí),Limit仍然非常有意義。用戶通常會(huì)對(duì)一些數(shù)據(jù)進(jìn)行排序、過濾和分頁(yè)。 這個(gè)例子中15條數(shù)據(jù)構(gòu)成一頁(yè),第一頁(yè)需要15條數(shù)據(jù),第二頁(yè)需要30條數(shù)據(jù),第三頁(yè)需要45條數(shù)據(jù),以此類推。在經(jīng)過一段時(shí)間分頁(yè)后,他們常常使用過濾來提煉數(shù)據(jù)。 即使他們不這樣做,他們也必須在此之前對(duì)大量的數(shù)據(jù)進(jìn)行分頁(yè),這將影響效率。當(dāng)然如果允許用戶點(diǎn)擊最后頁(yè),那么所有的數(shù)據(jù)都將被取出,這將非常影響效率。
提示:Spring框架的Controller和Struts框架的Action非常相像。
controller首先需要?jiǎng)?chuàng)建一個(gè)Limit。為了完成這個(gè)你需要得到一些關(guān)于Context和LimitFactory的幫助。
Context context = new HttpServletRequestContext(request); LimitFactory limitFactory = new TableLimitFactory(context); Limit limit = new TableLimit(limitFactory);
Context是一個(gè)處理取得屬性的接口,LimitFactory使用Context來找出用戶如何和eXtremeTable交互。 然后Limit使用LimitFactory來組裝自己。
為了初始化Limit,它將包含所有的有用的信息。這些信息包括數(shù)據(jù)將被如何排序和過濾,哪一頁(yè)將被顯示和是否允許被導(dǎo)出。
然而,Limit仍然需要得到行的信息,這樣正確的信息頁(yè)面才能被顯示給用戶。行信息包括開始行、結(jié)束行、當(dāng)前顯示行。 controller必須從service得到這些信息,而Service將從dao中得到這些信息。這里我只給出Controller端的代碼。
int totalRows = presidentsService.getTotalPresidents(limit.getFilterSet(), limit.isExported()); limit.setRowAttributes(totalRows, defaultRowsDisplayed);
limit需要得到所有的行來得到行的信息。service需要知道那些被過濾,不管這些數(shù)據(jù)是否要導(dǎo)出。為了設(shè)置行信息,默認(rèn)的一頁(yè)顯示的行數(shù)需要被設(shè)置。 這可以通過對(duì)TableTag的rowsDisplayed屬性設(shè)置一個(gè)確定的數(shù)值來實(shí)現(xiàn)。
現(xiàn)在我們只需要從services得到Collection數(shù)據(jù)。
Collection presidents = presidentsService.getPresidents(limit.getFilterSet(), limit.getSort(), limit.getRowEnd());
因?yàn)閘imit已經(jīng)包含所有信息,這將十分容易。所有需要做的就是傳入過濾器,排序和最后行的信息。 最后要做的是將Collections和totalRow這些信息傳送回JSP以便eXtremeTable知道如何顯示這些信息。
request.setAttribute("presidents", presidents); request.setAttribute("totalRows", new Integer(totalRows));
service需要和dao進(jìn)行交互來得到總行數(shù)和Collection。
controller需要到第一條信息就是總行數(shù)。
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一起來過濾結(jié)果集,它的工作方式是在Where語句后面增加更多的AND語句來修改查詢字符串。為此,你需要和Limit FilterSet一起工作。
FilterSet是一個(gè)過濾器對(duì)象數(shù)組,一個(gè)過濾器包括一個(gè)bean property和這個(gè)過濾器的值。或者,簡(jiǎn)單的說就是用戶想要過濾的行和他們輸入的值。這使得它非常容易交互。service只需要迭代所有的 FilterSet并調(diào)用dao來拼接查詢語句。(譯者注:過濾的實(shí)現(xiàn)方式是:在Where后面增加And語句來改變查詢語句以達(dá)到對(duì)數(shù)據(jù)進(jìn)行過濾的效果)
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信息,總行數(shù)。在一些情況下這就足夠,但是當(dāng)用戶導(dǎo)出數(shù)據(jù)時(shí)仍然存在一個(gè)潛在的問題。為了保持高效 service不允許導(dǎo)出超出一個(gè)最大行數(shù)的數(shù)據(jù)。
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一起來過濾結(jié)果集。
另外query字符串需要擴(kuò)展ORDER BY語句以便數(shù)據(jù)按照正確的方式進(jìn)行排序。Sort包含一個(gè)bean property和 sortOrder值(正序還是逆序)。service僅僅需要使用Sort來調(diào)用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字符串最后需要的修改就是增加數(shù)據(jù)庫(kù)特別的指令來limit將要被返回的結(jié)果集。這就是limitQuery() 方法的作用。
dao為service負(fù)責(zé)底層數(shù)據(jù)工作。
為了真正理解dao,query字符串需要被展示。
這就是得到數(shù)據(jù)的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 ";
這是得到總行數(shù)的query字符串:
private final static String totalPresidentsQuery = " SELECT count(*) FROM presidents ";
1.4.2.?Filter 和 Sort Query 字符串
兩個(gè)最有趣的方法就是過濾和排序。
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
現(xiàn)在query字符串修改能夠正確的進(jìn)行filter和sort,它還需要修改以便只取頁(yè)面顯示相關(guān)的數(shù)據(jù)。MySQL為s the limit命令。
public String limitQuery(int rowEnd, String query) { return query + " limit " + rowEnd; }
1.4.4.?取回總行數(shù)和Collection.
service需要的唯一東西就是:總行數(shù)和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是一個(gè)幫助處理JDBC查詢的Spring特殊類,作為一個(gè)callback來處理JDBC ResultSet。jdbcTemplate是對(duì)JDBC連接的抽象。
最后,這是service需要的默認(rèn)sort順序:
public String getDefaultSortOrder() { return " ORDER BY concat(first_name, ' ', last_name) "; }
為了設(shè)置全局屬性和設(shè)置,你需要使用Preferences特性,它現(xiàn)在使用一個(gè)屬性文件來實(shí)現(xiàn)。本文檔將很好地介紹如何在web.xml里設(shè)置Preferences, 以及一些需要被定義的通用屬性。在這里我非常樂意介紹一些關(guān)于Preferences的進(jìn)一步用法。
所有標(biāo)簽屬性表示一個(gè)可插接的接口,它可以通過給出實(shí)現(xiàn)的全路徑來設(shè)置。這為插接實(shí)現(xiàn)提供了一條便利的途徑。當(dāng)然這存在一些為過長(zhǎng)術(shù)語的設(shè)計(jì)和維護(hù)的考慮。 第一,對(duì)你的接口實(shí)現(xiàn)進(jìn)行硬編碼;第二,如果你需要在別的JSP中用到同一個(gè)接口實(shí)現(xiàn),你需要拷貝你全路徑。解決這兩個(gè)問題的有效辦法就是在Preferences中聲明一切。
下面列出的是可以在Preferences中申明的所有接口。Tag列展示的是eXtremeTable的標(biāo)簽,Attribute 列展示的是相關(guān)標(biāo)簽的對(duì)應(yīng)屬性。Interface列展示的是需要被實(shí)現(xiàn)的Java接口。Preference Key列展示的是 Preferences里對(duì)應(yīng)的健。
提示:當(dāng)在寫作本指南的時(shí)候,我意識(shí)到我忘記了讓標(biāo)簽ColumnsTag的autoGenerateColumns 屬性和Preferences協(xié)同工作。這將在下一版修正。
上表展示了如何聲明preference鍵,但是沒有解釋如何指定有意義的別名。如果你注意到preference鍵提供了一致的語法 tag.attribute,指定鍵的別名僅僅是在它的基礎(chǔ)上進(jìn)行擴(kuò)展。它的語法為: tag.attribute.alias。
eXtremeTable提供了一個(gè)名為RowCountCell定制的cell,它的作用是現(xiàn)實(shí)當(dāng)前的行數(shù)。我將在Preferences里使用ColumnTag cell聲明來示范RowCountCell的使用。
首先通過實(shí)現(xiàn)Cell接口或者擴(kuò)展AbstractCell來編寫具體的實(shí)現(xiàn)類。
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 (屬性文件)進(jìn)行聲明并給出別名。eXtremeTable在一個(gè)Preferences里保存所有的配置信息,你可以通過使用本地 Preferences的來覆蓋任何的這些屬性。
RowCountCell默認(rèn)的別名是rowCount:
column.cell.rowCount=org.extremecomponents.table.cell.RowCountCell
在ColumnTag中通過別名引用Cell:
<ec:column alias="count" cell="rowCount"/>
現(xiàn)在你可以通過rowCount來引用這個(gè)Cell,如果包名改變了你只需要對(duì)Preferences進(jìn)行修改。
提示:本示例中我使用了ColumnTag的別名屬性。別名屬性應(yīng)用在有兩列使用同樣的property,也應(yīng)用在列不直接和列的 bean property關(guān)聯(lián)的情況下。本示例就屬于這種情況。
Lyyb2001
|