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