Table of Contents
- 定制FilterCell
-
- 1. 引言
- 定制FilterRowsCallback
-
- 1. 引言
- Form指南
-
- 1. 引言
-
- 1.1. JSP
-
- 1.1.1. Form特性的技術(shù)說(shuō)明
- 1.1.2. Checkbox
- 1.1.3. Custom Cell
- 1.1.4. JavaScript
- 1.1.2. Checkbox
- 1.1.1. Form特性的技術(shù)說(shuō)明
- 1.2. Cell
- 1.3. Controller
-
- 1.3.1. 表標(biāo)簽動(dòng)作Controller
- 1.3.2. Form動(dòng)作Controller
- 1.3.3. 重新得到Checkbox的值
- 1.3.2. Form動(dòng)作Controller
- 1.3.1. 表標(biāo)簽動(dòng)作Controller
- Html視圖定制指南
-
- 1. 引言
-
- 1.1. View接口
- 1.2. Messages示例
- 1.1. View接口
- 攔截器使用指南
- Limit指南
-
- 1. 引言
-
- 1.1. JSP
- 1.2. Controller
- 1.3. Service
- 1.2. Controller
-
- 1.3.1. 取得總行數(shù)
- 1.3.2. 取得Collection
- 1.3.1. 取得總行數(shù)
- 1.4. DAO
-
- 1.4.1. 定義Query字符串
- 1.4.2. Filter 和 Sort Query 字符串
- 1.4.3. Limit Query String
- 1.4.4. 取回總行數(shù)和Collection.
- 1.4.5. 默認(rèn)的Sort順序
- 1.4.2. Filter 和 Sort Query 字符串
- 1.4.1. 定義Query字符串
- 1.1. JSP
- Preferences 指南
-
- 1. 引言
-
- 1.1. Preferences表
- 1.2. 指定Preference別名
- 1.1. Preferences表
列的filterCell屬性控制過(guò)濾器如何顯示,它和cell屬性非常相像并且也是實(shí)現(xiàn)Cell接口。馬上要定義的是默認(rèn)的和droplist這兩個(gè)過(guò)濾器cells。 默認(rèn)的是一個(gè)輸入框元素而droplist是一個(gè)下拉列表元素。當(dāng)然,如果你需要進(jìn)行一些定制你可以插接自己的實(shí)現(xiàn)。
最近,我被問(wèn)到是否能夠?qū)崿F(xiàn)一個(gè)過(guò)濾器cell,顯示已經(jīng)通過(guò)別的過(guò)濾器過(guò)濾得到數(shù)據(jù)子集。答案當(dāng)然是肯定的,而且這是我將在這里示范的。通常定制的 cell可以很容易被創(chuàng)建,這個(gè)示例將印證這點(diǎn)。在這個(gè)示例里last name列里顯示的將是通過(guò)first name過(guò)濾后的子集。如果沒(méi)有通過(guò) first name過(guò)濾那么所有值都將被顯示。
通常你只需要為過(guò)濾器cell實(shí)現(xiàn)Cell接口。然而,因?yàn)槲覀円獎(jiǎng)?chuàng)建的過(guò)濾器cell是一個(gè)下拉列表,我們可以通過(guò)擴(kuò)展 FilterDroplistCell來(lái)獲得它已經(jīng)提供的很多功能,F(xiàn)ilterDroplistCell是發(fā)行包已經(jīng)提供的cells之一。
我們需要覆蓋FilterDroplistCell的唯一方法是getFilterDropList()。這是整個(gè)類(lèi)的全部代碼:
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è)類(lèi)和父類(lèi),你會(huì)發(fā)現(xiàn)它們只有微小的區(qū)別。
首先需要注意的是我們需要找出first name是否已經(jīng)被過(guò)濾了。
String firstNameFilter = model.getLimit().getFilterSet().getValue("firstName");
然后我們需要判斷當(dāng)前bean的first name值是否和first name過(guò)濾器值相同。如果相同,將當(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è)值。如果沒(méi)有,我們就把它添加到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)類(lèi)的全路徑,但是使用Preferences更簡(jiǎn)潔。
column.filterCell.filteredDroplist=org.extremesite.cell.FilteredDroplistCell
在ColumnTag通過(guò)設(shè)置filterCell屬性來(lái)使用FilteredDroplistCell。
<ec:column property="lastName" filterCell="filteredDroplist"/>
如果不清楚Preferences和ColumnTag定義語(yǔ)法請(qǐng)參考Preferences指南。
FilterRowsCallback被用來(lái)過(guò)濾傳給eXtremeTable的Beans的Collection。 FilterRowsCallback的默認(rèn)實(shí)現(xiàn)是得到Beans或Maps的Collection,然后通過(guò)實(shí)現(xiàn)jakarta Predicate接口來(lái)進(jìn)行過(guò)濾。當(dāng)然,如果你需要進(jìn)行一些定制你可以插接自己的實(shí)現(xiàn)。
首先聲明,本示例代碼包含一些從原包中剪切、粘貼的代碼(雖然不是很多)。在 最初的最終發(fā)行包之后,值過(guò)濾得到進(jìn)一步改善使得更具復(fù)用性并更容易實(shí)現(xiàn),可能和定制cell代碼行數(shù)相同。 當(dāng)然,我被要求并非常樂(lè)意示范如何在當(dāng)前代碼基礎(chǔ)上實(shí)現(xiàn)定制過(guò)濾。這有非常清晰的hooks實(shí)現(xiàn),并且很容易實(shí)現(xiàn)。
本示例示范了如何調(diào)整代碼為過(guò)濾器提供一個(gè)精確的比較功能。當(dāng)前的實(shí)現(xiàn)是通過(guò)使用StringUtils.contains()方法進(jìn)行模糊比較。 本示例將使用StringUtils.equals()方法。你可以按照你的需要來(lái)調(diào)整代碼進(jìn)行更多定制。
首先你需要做的是創(chuàng)建一個(gè)實(shí)現(xiàn)Predicate接口的定制類(lèi)。Predicate要求我們實(shí)現(xiàn)evaluate()方法來(lái)判斷是否包含 當(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 類(lèi)來(lái)確認(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)類(lèi)的全路徑,但是使用Preferences更簡(jiǎn)潔。
table.filterRowsCallback.exactMatch=org.extremesite.callback.ExactMatchFilterRows
在TableTag通過(guò)設(shè)置filterRowsCallback屬性來(lái)使用ExactMatchFilterRows。
<ec:table filterRowsCallback="exactMatch"/>
如果不清楚Preferences和ColumnTag定義語(yǔ)法請(qǐng)參考Preferences指南。
eXtremeTable本質(zhì)上是一個(gè)form組件,所以我假定表被包在form里,所有的功能都被認(rèn)為是對(duì)form元素的操作。如果你想在表體中包含一些定制的form元素, 或者想將eXtremeTable嵌入到另外的form中,那么你就要使用表標(biāo)簽的form屬性用來(lái)參照最近的form。
為了示范form特性,我們要做的工作將分解為JSP,Cell和Controller。
下面列出的是checkbox示例的完整代碼。想要強(qiáng)調(diào)的主要事情是表標(biāo)簽form屬性設(shè)置為presForm,它參照被稱(chēng)為presForm的form元素。
同時(shí)請(qǐng)注意表標(biāo)簽的autoIncludeParameters屬性。進(jìn)行排序、過(guò)濾、分頁(yè)時(shí),默認(rèn)的eXtremeTable將保持所有傳至JSP頁(yè)面的參數(shù)。 這個(gè)特性對(duì)于內(nèi)部其他的form進(jìn)行排序、過(guò)濾、分頁(yè)時(shí),用于高效復(fù)制form元素同樣有效。可以設(shè)置 autoIncludeParameters屬性為false來(lái)固定它。
在這個(gè)form使用id屬性是因?yàn)閤htm標(biāo)準(zhǔn)的要求,同時(shí)你也可以使用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是你使用這個(gè)特性所必須知道的,為了更好的理解這個(gè)特性,介紹更多的關(guān)于內(nèi)部實(shí)現(xiàn)技術(shù)的細(xì)節(jié)是值得的。
如果您不特意指定form屬性,eXtremeTable自動(dòng)在表附近包上一個(gè)form。所有表的動(dòng)作例如:排序、過(guò)濾、分頁(yè)將自動(dòng)給一些隱藏的input元素賦值,然后提交這個(gè)form到表標(biāo)簽action屬性設(shè)置的Aciton。 這非常有效,除非您想要將自己的form元素設(shè)置到表體,或者想將這個(gè)表放到別的form里。
表標(biāo)簽form屬性參照最近的form,所有表的動(dòng)作例如:排序、過(guò)濾、分頁(yè)將自動(dòng)給一些隱藏的input元素賦值,但是現(xiàn)在 最近form的action屬性將要改變表標(biāo)簽的動(dòng)作。這非常重要,因?yàn)椋寒?dāng)排序、過(guò)濾、分頁(yè)時(shí),eXtremeTable能夠從一個(gè)controller得到數(shù)據(jù)集合 ,但是提交這個(gè)form到別的controller來(lái)處理這個(gè)form時(shí)需要對(duì)用戶的輸入進(jìn)行處理。然而,這些對(duì)于你使用表標(biāo)簽來(lái)說(shuō)都是透明的。 就像你現(xiàn)在做的那樣簡(jiǎn)單地設(shè)置表標(biāo)簽的action屬性,然后設(shè)置相關(guān)的form到你想提交的位置。
示例的第一列是checkbox。因?yàn)檫@列不需要參照bean的屬性,alias屬性用來(lái)唯一地標(biāo)識(shí)這列。你可以使用property 屬性,但是alias屬性使這列如何使用更清楚。alias屬性還被用來(lái)當(dāng)同樣的屬性被多列使用時(shí)唯一地標(biāo)識(shí)一列。
您也許想知道定制的cell是如何通過(guò)名稱(chēng)selectedPresident被參照的(cell="selectedPresident")。這是一個(gè) 對(duì)eXtremeTable的preferences特性更強(qiáng)的使用。所有要做的就是在extremecomponents.properties文件中添加一個(gè)屬性。 請(qǐng)參考Preferences來(lái)了解更多的信息
column.cell.selectedPresident=org.extremesite.cell.SelectedPresidentCell
column.cell.selectedPresident就是你定義的用來(lái)參照這個(gè)cell的名稱(chēng)。
當(dāng)然你也可以使用這個(gè)Cell的全名來(lái)進(jìn)行參照。
<ec:column
alias="checkbox"
title=" "
width="5px"
filterable="false"
sortable="false"
viewsAllowed="compact"
cell="org.extremesite.cell.SelectedPresidentCell"
/>
在屬性文件中定義參照更方便,它可以被任何JSP文件引用。如果類(lèi)名或包名改變的話你只需要對(duì)一個(gè)地方進(jìn)行修改。
定制的cell被用來(lái)生成checkbox,另外它也創(chuàng)建一個(gè)隱藏元素用來(lái)表示這個(gè)checkbox元素是否被選中。 當(dāng)用戶進(jìn)行排序、過(guò)濾、分頁(yè)時(shí),被選中的數(shù)據(jù)集合將被放到session里。
getExportDisplay()方法沒(méi)有返回值,因?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 來(lái)處理用戶的輸入并重新定向到另外的JSP頁(yè)面。當(dāng)排序、過(guò)濾、分頁(yè)時(shí),你可能有另外的controller來(lái)得到表使用的數(shù)據(jù)集合并重定向會(huì)本頁(yè)。或者你可以在同一個(gè)controller中分別處理。
checkbox示例里我使用一個(gè)controller來(lái)關(guān)聯(lián)表標(biāo)簽的action屬性。我也使用另外一個(gè)controller來(lái)關(guān)聯(lián)form元素的動(dòng)作。
這個(gè)controller負(fù)責(zé)調(diào)用SelectedPresidentsUtils來(lái)保存被選中的presidentIds到session里并回到同一頁(yè)。
SelectedPresidentsUtils.saveSelectedPresidentsIDs(request);
Collection presidents = presidentsService.getPresidents();
request.setAttribute("presidents", presidents);
這個(gè)controller負(fù)責(zé)通過(guò)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)常被問(wèn)到如何重新得到eXtremeTable中form元素的值。 它的原理是設(shè)置form輸入元素名字屬性值前面加上一些東西來(lái)唯一標(biāo)識(shí)元素
本示例中我關(guān)心的是以chkbx開(kāi)頭參數(shù)的元素。chkbx后面是唯一的關(guān)聯(lián)到checkbox的presidentId。它被用來(lái)判斷這個(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接口來(lái)生成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ù)雜的類(lèi)似swing的模型,原因是那需要?jiǎng)?chuàng)建大量的對(duì)象來(lái)處理對(duì)應(yīng)的內(nèi)部工作。 eXtremeTable以高效為目標(biāo),我也想在視圖的實(shí)現(xiàn)上貫徹這種想法,所以我決定創(chuàng)建一系列的靜態(tài)構(gòu)造器類(lèi)來(lái)實(shí)現(xiàn)分解的最小功能。你可以通過(guò)組合這些功能來(lái)實(shí)現(xiàn)你的定制視圖。
學(xué)習(xí)定制視圖的最好途徑是閱讀已經(jīng)存在的視圖的源代碼,修改它來(lái)滿足你的需求。如果我示范所有東西的話,這篇指南將變的非常冗長(zhǎng)。取而代之的是我將直接修改默認(rèn)視圖的工具條作為定制視圖的一個(gè)示例。 對(duì)于不同構(gòu)造器的具體細(xì)節(jié)我建議你閱讀源代碼。我也將盡量更新javadocs來(lái)提供更好的幫助。
實(shí)現(xiàn)View接口的類(lèi)有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);
}
在這篇指南里我將直接修改工具條來(lái)實(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ò)展了虛擬視圖來(lái)修改工具條和狀態(tài)條。如何修改工具條和(或)狀態(tài)條也是開(kāi)發(fā)人員問(wèn)的最多問(wèn)題。
默認(rèn)視圖的工具條位于表的上方包括翻頁(yè)鏈接和標(biāo)題。工具條使用TwoColumnTableLayout,它是一個(gè)用于提供在自己表中實(shí)現(xiàn)左右兩列布局的虛擬類(lèi)。 它將實(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()方法用來(lái)阻止或?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è)使用了文字來(lái)替代圖片顯示。我真正希望示范的是你需要做的:找到正確的構(gòu)造器類(lèi)并且僅僅是擴(kuò)展HtmlBuilder的標(biāo)簽。 構(gòu)造器類(lèi)對(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è)視圖的完整有效的類(lèi)名,不過(guò)Preferences更為簡(jiǎn)潔。
table.view.messages=org.extremesite.view.MessagesView
TableTag也將設(shè)置視圖屬性來(lái)使用MessagesView視圖。
<ec:table view="messages">
如果不清楚Preferences和TableTag定義語(yǔ)法請(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中訪問(wèn)的標(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í)類(lèi)型來(lái)替換僅僅使用字符串類(lèi)型的對(duì)象。beans是被模型使用并且是你需要使用攔截特性修改的對(duì)象。 所有的攔截器接口都定義了一個(gè)add方法, add方法被用來(lái)處理模型bean第一次創(chuàng)建時(shí)的屬性。行和列的攔截器還有一個(gè)modify 方法。modify方法可以在當(dāng)行和類(lèi)進(jìn)行處理是對(duì)屬性值進(jìn)行操作。
下面列出了具有攔截特性的標(biāo)簽和他們需要被實(shí)現(xiàn)的接口,Bean欄顯示了被模型創(chuàng)建的Bean。
標(biāo)簽 | 接口 | Bean |
---|---|---|
TableTag | org.extremecomponents.table.intercept.InterceptTable | org.extremecomponents.table.bean.Table |
RowTag | org.extremecomponents.table.intercept.InterceptRow | org.extremecomponents.table.bean.Row |
ColumnTag | org.extremecomponents.table.intercept.InterceptColumn | org.extremecomponents.table.bean.Column |
ExportTag | org.extremecomponents.table.intercept.InterceptExport | org.extremecomponents.table.bean.Export |
示范攔截特性的完美示例就是根據(jù)一定的標(biāo)準(zhǔn)來(lái)對(duì)行進(jìn)行高亮顯示,這也是我們將要完成的示例。它很短也很簡(jiǎn)單,不過(guò)它實(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定義語(yǔ)法請(qǐng)參考Preferences指南。
在你需要處理大量數(shù)據(jù)時(shí)你應(yīng)該考慮使用eXtremeTable的Limit特性。Limit這個(gè)名字來(lái)自MySQL的limit 命令,Limit接口的目的就是如何對(duì)表的結(jié)果集進(jìn)行l(wèi)imit處理。Limit實(shí)現(xiàn)知道當(dāng)排序、過(guò)濾、分頁(yè)、導(dǎo)出時(shí),用戶如何與表互相作用。有了這些信息你 將能夠使用可能是最有效的方式顯示正確的過(guò)濾、排序后的請(qǐng)求頁(yè)面。
為了示范Limit特性,我將要做的工作將分解為JSP、Controller、Service和DAO。這示范了一種使用分層的方式來(lái)處理 Limit。你可以根據(jù)自己的需要來(lái)增加或減少層。本示例也使用了Spring框架來(lái)重新得到使用Spring的JDBC取得的數(shù)據(jù),因此你的代碼看起來(lái)可能有點(diǎn)不同。eXtremeTable的一個(gè)特點(diǎn)就是不依賴(lài)任何框架和容器。
為了使用Limit特性,eXtremeTable需要使用limit特定的RetrieveRowsCallback、 FilterRowsCallback和SortRowsCallback接口。eXtremeComponents提供了每個(gè)接口的一個(gè)實(shí)現(xiàn),可以簡(jiǎn)單地通過(guò)設(shè)置每個(gè)屬性值為limit來(lái)簡(jiǎn)單來(lái)使用。
<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沒(méi)有提供特性。在我的示例中我考慮最壞的情況你的數(shù)據(jù)庫(kù)不支持這種特性。
即使你的數(shù)據(jù)庫(kù)不提供取得特定行的特性,當(dāng)你考慮用戶如何和表協(xié)同工作時(shí),Limit仍然非常有意義。用戶通常會(huì)對(duì)一些數(shù)據(jù)進(jìn)行排序、過(guò)濾和分頁(yè)。 這個(gè)例子中15條數(shù)據(jù)構(gòu)成一頁(yè),第一頁(yè)需要15條數(shù)據(jù),第二頁(yè)需要30條數(shù)據(jù),第三頁(yè)需要45條數(shù)據(jù),以此類(lèi)推。在經(jīng)過(guò)一段時(shí)間分頁(yè)后,他們常常使用過(guò)濾來(lái)提煉數(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來(lái)找出用戶如何和eXtremeTable交互。 然后Limit使用LimitFactory來(lái)組裝自己。
為了初始化Limit,它將包含所有的有用的信息。這些信息包括數(shù)據(jù)將被如何排序和過(guò)濾,哪一頁(yè)將被顯示和是否允許被導(dǎo)出。
然而,Limit仍然需要得到行的信息,這樣正確的信息頁(yè)面才能被顯示給用戶。行信息包括開(kāi)始行、結(jié)束行、當(dāng)前顯示行。 controller必須從service得到這些信息,而Service將從dao中得到這些信息。這里我只給出Controller端的代碼。
int totalRows = presidentsService.getTotalPresidents(limit.getFilterSet(), limit.isExported());
limit.setRowAttributes(totalRows, defaultRowsDisplayed);
limit需要得到所有的行來(lái)得到行的信息。service需要知道那些被過(guò)濾,不管這些數(shù)據(jù)是否要導(dǎo)出。為了設(shè)置行信息,默認(rèn)的一頁(yè)顯示的行數(shù)需要被設(shè)置。 這可以通過(guò)對(duì)TableTag的rowsDisplayed屬性設(shè)置一個(gè)確定的數(shù)值來(lái)實(shí)現(xiàn)。
現(xiàn)在我們只需要從services得到Collection數(shù)據(jù)。
Collection presidents = presidentsService.getPresidents(limit.getFilterSet(), limit.getSort(), limit.getRowEnd());
因?yàn)閘imit已經(jīng)包含所有信息,這將十分容易。所有需要做的就是傳入過(guò)濾器,排序和最后行的信息。 最后要做的是將Collections和totalRow這些信息傳送回JSP以便eXtremeTable知道如何顯示這些信息。
request.setAttribute("presidents", presidents);
request.setAttribute("totalRows", new Integer(totalRows));
service需要和dao進(jìn)行交互來(lái)得到總行數(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一起來(lái)過(guò)濾結(jié)果集,它的工作方式是在Where語(yǔ)句后面增加更多的AND語(yǔ)句來(lái)修改查詢(xún)字符串。為此,你需要和Limit FilterSet一起工作。
FilterSet是一個(gè)過(guò)濾器對(duì)象數(shù)組,一個(gè)過(guò)濾器包括一個(gè)bean property和這個(gè)過(guò)濾器的值。或者,簡(jiǎn)單的說(shuō)就是用戶想要過(guò)濾的行和他們輸入的值。這使得它非常容易交互。service只需要迭代所有的 FilterSet并調(diào)用dao來(lái)拼接查詢(xún)語(yǔ)句。(譯者注:過(guò)濾的實(shí)現(xiàn)方式是:在Where后面增加And語(yǔ)句來(lái)改變查詢(xún)語(yǔ)句以達(dá)到對(duì)數(shù)據(jù)進(jìn)行過(guò)濾的效果)
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è)潛在的問(wèn)題。為了保持高效 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一起來(lái)過(guò)濾結(jié)果集。
另外query字符串需要擴(kuò)展ORDER BY語(yǔ)句以便數(shù)據(jù)按照正確的方式進(jìn)行排序。Sort包含一個(gè)bean property和 sortOrder值(正序還是逆序)。service僅僅需要使用Sort來(lái)調(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ù)特別的指令來(lái)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 ";
兩個(gè)最有趣的方法就是過(guò)濾和排序。
filter看起來(lái)像這樣:
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語(yǔ)句到query字符串。
sort看起來(lái)非常類(lèi)似:
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語(yǔ)句到query字符串。
現(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;
}
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查詢(xún)的Spring特殊類(lèi),作為一個(gè)callback來(lái)處理JDBC ResultSet。jdbcTemplate是對(duì)JDBC連接的抽象。
為了設(shè)置全局屬性和設(shè)置,你需要使用Preferences特性,它現(xiàn)在使用一個(gè)屬性文件來(lái)實(shí)現(xiàn)。本文檔將很好地介紹如何在web.xml里設(shè)置Preferences, 以及一些需要被定義的通用屬性。在這里我非常樂(lè)意介紹一些關(guān)于Preferences的進(jìn)一步用法。
所有標(biāo)簽屬性表示一個(gè)可插接的接口,它可以通過(guò)給出實(shí)現(xiàn)的全路徑來(lái)設(shè)置。這為插接實(shí)現(xiàn)提供了一條便利的途徑。當(dāng)然這存在一些為過(guò)長(zhǎng)術(shù)語(yǔ)的設(shè)計(jì)和維護(hù)的考慮。 第一,對(duì)你的接口實(shí)現(xiàn)進(jìn)行硬編碼;第二,如果你需要在別的JSP中用到同一個(gè)接口實(shí)現(xiàn),你需要拷貝你全路徑。解決這兩個(gè)問(wèn)題的有效辦法就是在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)的健。
Tag | Attribute | Interface | Preference Key |
---|---|---|---|
TableTag | filterRowsCallback | org.extremecomponents.table.callback.FilterRowsCallback | table.filterRowsCallback |
TableTag | intercept | org.extremecomponents.table.intercept.InterceptTable | table.intercept |
TableTag | retrieveRowsCallback | org.extremecomponents.table.callback.RetrieveRowsCallback | table.retrieveRowsCallback |
TableTag | sortRowsCallback | org.extremecomponents.table.callback.SortRowsCallback | table.sortRowsCallback |
TableTag | state | org.extremecomponents.table.state.State | table.state |
TableTag | view | org.extremecomponents.table.view.View | table.view |
RowTag | intercept | org.extremecomponents.table.intercept.InterceptRow | row.intercept |
ColumnTag | calc | org.extremecomponents.table.calc.Calc | column.calc |
ColumnTag | cell | org.extremecomponents.table.cell.Cell | column.cell |
ColumnTag | filterCell | org.extremecomponents.table.cell.Cell | column.filterCell |
ColumnTag | headerCell | org.extremecomponents.table.cell.Cell | column.headerCell |
ColumnTag | intercept | org.extremecomponents.table.intercept.InterceptColumn | column.intercept |
ExportTag | intercept | org.extremecomponents.table.intercept.InterceptExport | export.intercept |
ExportTag | view | org.extremecomponents.table.view.View | export.view |
ExportTag | viewResolver | org.extremecomponents.table.filter.ViewResolver | export.viewResolver |
提示:當(dāng)在寫(xiě)作本指南的時(shí)候,我意識(shí)到我忘記了讓標(biāo)簽ColumnsTag的autoGenerateColumns 屬性和Preferences協(xié)同工作。這將在下一版修正。
上表展示了如何聲明preference鍵,但是沒(méi)有解釋如何指定有意義的別名。如果你注意到preference鍵提供了一致的語(yǔ)法 tag.attribute,指定鍵的別名僅僅是在它的基礎(chǔ)上進(jìn)行擴(kuò)展。它的語(yǔ)法為: tag.attribute.alias。
eXtremeTable提供了一個(gè)名為RowCountCell定制的cell,它的作用是現(xiàn)實(shí)當(dāng)前的行數(shù)。我將在Preferences里使用ColumnTag cell聲明來(lái)示范RowCountCell的使用。
首先通過(guò)實(shí)現(xiàn)Cell接口或者擴(kuò)展AbstractCell來(lái)編寫(xiě)具體的實(shí)現(xiàn)類(lèi)。
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里保存所有的配置信息,你可以通過(guò)使用本地 Preferences的來(lái)覆蓋任何的這些屬性。
RowCountCell默認(rèn)的別名是rowCount:
column.cell.rowCount=org.extremecomponents.table.cell.RowCountCell
在ColumnTag中通過(guò)別名引用Cell:
<ec:column alias="count" cell="rowCount"/>
現(xiàn)在你可以通過(guò)rowCount來(lái)引用這個(gè)Cell,如果包名改變了你只需要對(duì)Preferences進(jìn)行修改。
提示:本示例中我使用了ColumnTag的別名屬性。別名屬性應(yīng)用在有兩列使用同樣的property,也應(yīng)用在列不直接和列的 bean property關(guān)聯(lián)的情況下。本示例就屬于這種情況。