版本1.0.0
(更新中...)
Table of Contents
列的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()方法。你可以按照你的需要来调整代码进行更多定制。
首先你需要做的是创建一个实现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文件引用。如果类名或包名改变的话你只需要对一个地方进行修改。
定制的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。
标签 | 接口 | 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 |
示范拦截特性的完美示例就是根据一定的标准来对行进行高亮显示,这也是我们将要完成的示例。它很短也很简单,不过它实现的概念同样适用于每一个拦截器接口。
我们需要做的第一件事就是实现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 ";
两个最有趣的方法就是过滤和排序。
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字符串。
现在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连接的抽象。
为了设置全局属性和设置,你需要使用Preferences特性,它现在使用一个属性文件来实现。本文档将很好地介绍如何在web.xml里设置Preferences, 以及一些需要被定义的通用属性。在这里我非常乐意介绍一些关于Preferences的进一步用法。
所有标签属性表示一个可插接的接口,它可以通过给出实现的全路径来设置。这为插接实现提供了一条便利的途径。当然这存在一些为过长术语的设计和维护的考虑。 第一,对你的接口实现进行硬编码;第二,如果你需要在别的JSP中用到同一个接口实现,你需要拷贝你全路径。解决这两个问题的有效办法就是在Preferences中声明一切。
下面列出的是可以在Preferences中申明的所有接口。Tag列展示的是eXtremeTable的标签,Attribute 列展示的是相关标签的对应属性。Interface列展示的是需要被实现的Java接口。Preference Key列展示的是 Preferences里对应的健。
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 |
提示:当在写作本指南的时候,我意识到我忘记了让标签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关联的情况下。本示例就属于这种情况。