-
Servlet 2.3或更高版本
-
JDK 1.3.1或更高版本
最小的Jars要求:
-
commons-beanutils 1.6
-
commons-collections 3.0
-
commons-lang 2.0
-
commons-logging 1.0.4
-
standard 1.0.2
PDF导出需要的Jars:
-
minimum jars (above)
-
avalon-framework 4.0
-
batik 1.5-fop-0.20-5
-
fop 0.20.5
-
xalan 2.5.1
-
xercesImpl 2.6.1
-
xml-apis 2.0.2
XLS导出需要的Jars:
-
minimum jars (above)
-
poi-2.5.1.jar
从sourceforge 下载发行包。(http://sourceforge.net/projects/extremecomp/)
在压缩文件里你能找到开始使用需要的所有东西:
-
extremecomponents.jar
-
extremecomponents.tld
-
extremecomponents.css
-
默认的一组图片
-
源代码
-
test.jsp (用于确认安装是否正确)
将extremecomponents.jar文件拷贝到你的工程的/WEB-INF/lib目录下。
处理TLD文件有两种方式。 你可以把extremecomponents.tld文件放到WEB-INF目录下的任何地方。 不过,为了便于管理,我喜欢把我的TLD文件都放到/WEB-INF/tld目录下。你需要根据你的extremecomponents.tld 文件的位置来修改/WEB-INF/web.xml文件的标签映射。
<taglib>
<taglib-uri>/tld/extremecomponents</taglib-uri>
<taglib-location>/WEB-INF/tld/extremecomponents.tld</taglib-location>
</taglib>
随后,你需要向下面一样在你的JSP里把eXtremeTable包含进来:
<%@ taglib uri="/tld/extremecomponents" prefix="ec" %>
如果你的servlet容器支持JSP 1.2 (或更高版本),它将能够自动发现TLD文件,那么你什么也不需要做。 当extremecomponents.jar被容器加载的时候,在它的META-INF目录下的extremecomponents.tld文件将被找到。 这时,你需要向下面一样在你的JSP里把eXtremeTable包含进来:
<%@ taglib uri="http://www.extremecomponents.org" prefix="ec" %>
为了使用eXtremeTable样式,从styles目录拷贝extremecomponents.css到你存放.css脚本的地方。 当然在JSP页面里,你需要提供一个到CSS的链接。就像我将我的样式表放在/styles目录下。
<%@ taglib uri="/tld/c" prefix="c" %>
<link rel="stylesheet" type="text/css" href="<c:url value="/styles/extremecomponents.css"/>">
为了使导出功能有效,你需要设置导出过滤器。这是一个仅用于导出功能的可选配置。
如下所示在/WEB-INF/web.xml里配置过滤器:
<filter>
<filter-name>eXtremeExport</filter-name>
<filter-class>org.extremecomponents.table.filter.ExportFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>eXtremeExport</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器还有一个可选的初始化参数,用于决定什么时候生成报头(headers)。我发现大多数的servlet容器 倾向于在调用过滤器的doFilter()方法后才设置响应报头(response headers)。然而,一些servlet容器只有在 调用过滤器的doFilter()方法前设置响应报头,过滤器才能正常工作。默认的方法是调用过滤器的doFilter()方法后 设置响应报头,你可以通过使用responseHeadersSetBeforeDoFilter这个初始化参数调整它。
<filter>
<filter-name>eXtremeExport</filter-name>
<filter-class>org.extremecomponents.table.filter.ExportFilter</filter-class>
<init-param>
<param-name>responseHeadersSetBeforeDoFilter</param-name>
<param-value>true</param-value>
</init-param>
</filter>
如果你使用了Sitemesh,你将需要包含SitemeshPageFilter。SitemeshPageFilter扩展了正常的 sitemesh的PageFilter,它使得正在进行导出的JSP页面不被修饰。
如下所示在/WEB-INF/web.xml里配置过滤器:
<filter>
<filter-name>Sitemesh</filter-name>
<filter-class>org.extremecomponents.table.filter.SitemeshPageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用发行包的test.jsp来测试安装。将test.jsp拷贝到web应用的最顶层, 默认的图片文件在 /images/table/子目录下。为了测试,创建相应的目录并将拷贝所有需要的图片。所有工作都完成后,你可以在浏览 器里运行test.jsp了。
提示: 我不提倡在JSP里使用脚本(scriplets),但为了不用使用框架而能进行快速测试,在test.jsp 使用脚本是唯一的办法。
TableTag用来设定什么被显示并且如何进行显示。默认的eXtremeTable在servlet范围(按照page,request, session,applicaton的顺序)寻找具有名称和items属性设置相同的Beans集合(如前章所述它指Beans和Maps两种集合)。 表将遍历所有列,它使用var属性将当前行对应的bean从集合传到page范围,因此你可以从page范围中重新得到这些数据 进行操作。tableId用来唯一标识表,如果在JSP页面里包含两个或两个以上的表时需要设置它。
President bean定义如下:
public class President implements Serializable {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Beans集合需要被组装并传到servlet范围中。我喜欢使用Spring框架,因此示例将使用Spring框架的 控制器(Controller)。如果你正在使用Struts,它和Action的功能类似。如果你使用别的东西,比如直接使用 servlets,你只需要明白我所做的只是组装Beans集合并传到request范围中。
public class Presidents extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
List presidents = new ArrayList();
President president = new President();
president.setFirstName("George");
president.setLastName("Washington");
presidents.add(president);
president = new President();
president.setFirstName("John");
president.setLastName("Adams");
presidents.add(president);
request.setAttribute("presidents", presidents);
return new ModelAndView("/demo/presidents.jsp");
}
现在你可以构造表了:
<%@ taglib uri="/tld/extremecomponents" prefix="ec" %>
<ec:table
items="presidents"
var="pres"
imagePath="${pageContext.request.contextPath}/images/*.gif"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:column property="firstName"/>
<ec:column property="lastName"/>
${pres.lastName}
</ec:column>
</ec:table>
从本示例中你应该知道我们将名为presidents的Beans集合以presidents为名称放到request中。 为了使表知道如何找到这个Beans集合,我们设置TableTag的items属性为presidents。同时我们定义 了两列:firstName和lastName。firstName列是最普通的用法:我们仅仅想让这列从当前bean中得到相应 firstName的值;lastName列示另外一种用法:明确取得值。
从一列中明确取得值非常有用,但是你需要理解表是如何构造行的。为了构造行,表需要对所有行进行 rowsDisplayed属性设定次数的迭代。每次迭代都从Beans里取得下一个bean并使用var属性设定的名称传入page 范围。也可以说每次迭代你都访问的是集合中当前行对应的bean。
为了显示图片需要设置imagePath属性:
<ec:table
items="presidents"
var="pres"
imagePath="${pageContext.request.contextPath}/images/*.gif"
>
...
</ec:table>
eXtremeTable将找到一个目录下的所有图片并使用特殊的语法来定义他们是那类图片。 本示例中所有的图片都直接保存在web上下文的images目录下。*.gif使eXtremeTable知道所 有的图片都是GIF格式的。在我们讨论preferences后,你将发现你可以你可以通过在 extremecomponents.properties文件中设定这个属性,而不用再整个应用的每个eXtremeTable 中包含它。
eXtremeTable内嵌了过滤和排序功能,你只需要决定是否需要使用他们。你要使用的属性是 filterable和sortable,他们都是布尔值并且默认值是true。默认的所有特性都有效,你可以按照 需要来关掉一些特性。比如,如果你不想使用排序或过滤你可以把他们的属性设为false。
<ec:table
items="presidents"
var="pres"
imagePath="${pageContext.request.contextPath}/images/*.gif"
action="${pageContext.request.contextPath}/presidents.run"
filterable="false"
sortable="false"
>
...
</ec:table>
如果你仍不确信,你可以来验证他们。首先,设置filterable和sortable为true,你将看到 eXtremeTable允许你输入关键词来过滤结果集,它也允许你通过在页头(header)上滚动鼠标来排序。 然后,设置filterable和sortable为fale,你将发现所有这些特性都不允许使用。
本示例需要指出的是使用action属性,action被用来告诉eXtremeTable当过滤或排序时如何回 到当前的页面。本例中我通过Spring框架的controller(在这里是presidents.run)来得到Beans集合。 你不需要担心传参问题,eXtremeTable将保存所有的参数并将它们和过滤器、排序、分页一起传递给 Beans集合。更详细的信息请参考ParameterTag。
默认地eXtremeTable一页将显示15行。你可以通过设定rowsDisplayed属性为你想显示行数的数 值来改变它。rowsDisplayed也可以在extremecomponents.properties文件中设定。(参考Preferences)。
提示:如果你想在一页中显示所有行,只需要设置showPagination为false。
TableTag关联了很多样式属性:
<ec:table
cellspacing="0"
cellpadding="0"
border="0"
width="80%"
style=""
styleClass=""
/>
所有这些都是可选的。
表新增了两个属性:state和stateAttr。state属性参照State接口并能插接如何保存表的状态的不同实现。
State借口如下:
public interface State {
public void saveParameters(TableModel model, Map parameters);
public Map getParameters(TableModel model);
}
state属性使用预设的四种状态(default、notifyToDefault、persist和notifyToPersist)之一, 你也可以插接自己的实现。default状态不维持任何状态;persist状态没有任何参数传入,将一直维持表的状态; notifyToDefault状态将一直维持表的状态直到你传入参数告诉它回到default状态;notifyToPersist状态 将一直维持当前状态直到你传入参数告诉它维持persisted状态。
stateAttr为指定参数提供了一条途径,你也可以使用属性文件在全局范围内指定它。 为了向后兼容,默认参数一直为useSessionFilterSort。
如果你想state按照不同方式工作你只要实现State接口,然后使用TableTag的state属性来指定实现类的 全路径。
作为一条首要规则当使用state属性时,需要指定tableId。这是因为state使用tableId为名保存在session里。 如果tableId不唯一,eXtremeTable将覆盖另一个同名的内容。tableId默认值为ec。
为了保持一致性,所有的显示特性都命名为showXXXX。他们包括showPagination、showStatusBar、 showTooltips、和showExports。
title属性将在表的上方显示标题,标题的位置根据使用的视图不同而不同。当前默认视图中标题位于表的上方 工具条的左边。更详细的信息请参考View。
你会发现还有一些属性没有被探讨,因为他们将在其他章探讨。autoIncludeParameters在ParameterTag里被探讨; retrieveRowsCallback,sortRowsCallback和filterRowsCallback在Callbacks里被探讨。
大多数标签包含一系列的固定属性,这样那些已经实现的功能能够被使用。然而,eXtremeTable具有一种更具弹性的架构, 你可以添加自己的标签属性实现更多的定制工作。此外,eXtremeTable提供了非常清晰的钩子(hooks)允许你得到那些定制的 标签属性来做一些你需要的工作。
通过addExtendedAttributes()方法将扩展属性包含到eXtremeTable里:
public void addExtendedAttributes(Table table);
如果方法被覆盖TableTag将调用它。你需要做的就是扩展TableTag,覆盖addExtendedAttributes()方法,然后添加自己 的属性到表对象中。一个定制的TreeTag示例如下:
public class TreeTag extends TableTag {
private String parentAttribute;
private String identifier;
public void setParentAttribute(String parentAttribute) {
this.parentAttribute = parentAttribute;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public void addExtendedAttributes(Table table) {
table.addAttribute(TableConstants.PARENT_ATTRIBUTE, TagUtils.evaluateExpressionAsString("parentAttribute",
parentAttribute, this, pageContext));
table.addAttribute(TableConstants.IDENTIFIER, TagUtils.evaluateExpressionAsString("identifier",
identifier, this, pageContext));
table.setFilterRowsCallback("org.extremecomponents.tree.ProcessTreeRowsCallback");
table.setSortRowsCallback("org.extremecomponents.tree.ProcessTreeRowsCallback");
}
}
现在你添加了属性值到table对象。
另外,你也可以定制自己的标签和自己的TLD文件。你不需要修改extremecomponents.tld文件。 你能象使用eXtremeTable里的标签一样使用自己的标签,除了使用你自己标签的参照。假如你的标签参照为mycompany 并且标签为customTable,你可以像下面一样使用他们:
<mycompany:customTable
items="presidents"
action="${pageContext.request.contextPath}/public/demo/presidents.jsp"
title="Presidents"
>
<ec:row>
<ec:column property="nickName"/>
</ec:row>
</mycompany:customTable>
ColumnTag用来定义表中的列。
示例President Bean:
public class President implements Serializable {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
下例生成firstName和lastName列:
<ec:table
items="presidents"
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
/>
<ec:row>
<ec:column property="firstName"/>
<ec:column property="lastName">
${pres.lastName}
</ec:column>
</ec:row>
</ec:table>
通过对TableTag的讨论,已经知道列可以通过动态或精确的方式得到他们的值。 firstName列动态地取得相应的值,列找到当前的bean并调用相应的getFirstName()取得值。 lastName列明确地从当前bean取得值,它要求你自己取得相应的值。如下例:
<ec:table
items="presidents"
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="lastName">
${pageScope.pres.lastName}
</ec:column>
</ec:row>
</ec:table>
从page范围中取得名为pres的bean并得到它对应的lastName属性值。如果你正使用 Beans集合请确认具有对应的getter方法;如果使用Maps集合则不需要任何别的动作, eXtremeTable能够通过属性名从Map中得到对应的值。
提供这种可选取值方法的主要原因是使你能够对其他类型的html标签提供动作支持,例如显示 一幅图片或者通过定义href使该列成为到其它页的一个链接。
<ec:table
items="presidents"
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="lastName">
<a href="http://goto.president.detail">${pageScope.pres.lastName}</a>
</ec:column>
</ec:row>
</ec:table>
切记bean中所有的属性都是可访问的,因此你甚至可以通过firstName属性 来显示下一页。请注意firstName属性是如何作为URL字符串传输的。
<ec:table
items="presidents"
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
/>
<ec:row>
<ec:column property="lastName">
<a href="http://goto.president.detail?firstName=${pageScope.pres.firstName}">
${pageScope.presidents.lastName}
</a>
</ec:column>
</ec:row>
</ec:table>
我将不再在任何示例中强调pageScope。JSP标签总是最先在pageScope中寻找任何对像, 因此我们总是能安全地返回正确的bean。
每一列总是被实现Cell接口的对象修饰,你可以认为Cell是一个为了html显示或导出而返回格式化值的对象。 发行包包含的Cell有DisplayCell、DateCell、 NumberCell和RowCountCell。 DisplayCell是仅仅显示列值的默认cell;DateCell使用parse属性(可选)和format属性来格式化对应的属性值; NumberCell使用format属性来格式化对应的属性值;RowCountCell显示当前行。
提示:为了避免混乱并提高灵活性Cell接口已经被修改。而且对于区别 如何处理html和导出显示值也不是很清晰。以前列值作为html显示,列的propertyValue作为导出使用。 另外列值和propertyValue已经重写,他们以前在view中是不能被访问的。
cell现在是singleton并且不再线程安全,改变的原因是为了Cell接口能更简单地被使用。 init()和destroy()方法作为singleton更灵活但是处于一种混乱的状态。
Cell接口如下:
public interface Cell {
/**
* The display that will be used for the exports.
*/
public String getExportDisplay(TableModel model, Column column);
/**
* The html that will be displayed in the table.
*/
public String getHtmlDisplay(TableModel model, Column column);
}
现在得到导出和html显示存在明显的区别。更重要的,需要返回字符串。列值和属性值不再 需要设置。
DisplayCell是扩展AbstractCell的最简单的Cell。AbstractCell定义 的虚拟方法getCellValue用来返回cell的值。虽然AbstractCell在一些情况下是有用的, 但更多情况下只需要直接实现Cell接口。
DisplayCell:
public class DisplayCell extends AbstractCell {
public String getExportDisplay(TableModel model, Column column) {
return column.getPropertyValueAsString();
}
protected String getCellValue(TableModel model, Column column) {
return column.getValueAsString();
}
}
AbstractCell:
public abstract class AbstractCell implements Cell {
public String getExportDisplay(TableModel model, Column column) {
return getCellValue(model, column);
}
public String getHtmlDisplay(TableModel model, Column column) {
HtmlBuilder html = new HtmlBuilder();
CellBuilder.tdStart(html, column);
CellBuilder.tdBody(html, getCellValue(model, column));
CellBuilder.tdEnd(html);
return html.toString();
}
/**
* A convenience method to get the display value.
*/
protected abstract String getCellValue(TableModel model, Column column);
}
现在你应该知道Cell是多么简单。只需通过实现Cell接口或扩展AbstractCell来定制你自己的Cell, 并设置列标签的Cell属性为类的全路径。例如: 如果你定制了一个名为MyCell的Cell,那么你可以像下面一样使用它:
<ec:column property="firstName" cell="com.mycompany.cell.MyCell"/>
如果你改变列的数据,那么过滤或排序可能没有意义。切记我的意思是如果你人为地改变数据, 而不是使用样式对它进行包装或作为<a href>包含。 如果你的定制cell显示数据的树状视图,或者是一幅图片, 那么过滤和排序等一切逻辑操作都是没有意义的。
列的filterCell属性控制过滤器如何显示,它和cell属性非常相像并且也是实现Cell接口。 已经定义了两个过滤器cells:默认的和droplist。默认的是一个输入框元素,除非你确信你需要使这列可以进行过滤, 否则你不需要做任何事。
你可以像下面一样使用droplist过滤器Cell:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="status" filterCell="droplist"/>
</ec:row>
</ec:table>
filterCell也允许你定义定制的过滤器,所有你必须做的就是实现Cell接口或者扩展AbstractCell, 并设置列标签的Cell属性为类的全路径。例如,如果你定制了一个名为MyCell的Cell,那么你可以像下面一样使用它:
<ec:column property="firstName" filterCell="com.mycompany.cell.MyFilterCell"/>
参阅Cell节了解如何创建你自己定制Cells的更多信息。
headerCell属性控制headers如何显示,它和cell属性非常相像并且也是实现Cell接口。 默认header cell作为文本显示,包含排序逻辑。
headerCell也允许你定义定制的过滤器,所有你必须做的就是实现Cell接口或者扩展AbstractCell, 并设置列标签的Cell属性为类的全路径。例如,如果你定制了一个名为MyCell的Cell,那么你可以像下面一样使用它:
<ec:column property="firstName" headerCell="com.mycompany.cell.MyHeaderCell"/>
参阅Cell节了解如何创建你自己定制Cells的更多信息。
ColumnTag关联了很多样式属性:
<ec:column
width=""
style=""
styleClass=""
headerStyle=""
headerClass=""
filterStyle=""
filterClass=""
/>
所有这些都是可选的。style属性定义列内联的样式;styleClass允许你定义一个列显示的css类; headerClass属性允许你改变header列的css类;filterClass属性允许你改变filter列的css类。
解析和格式化属性被用在日期和货币的显示上。
和date交互的工作依赖于你的bean属性是否是一个字符串或者是一个Date对象。 如果是一个字符串那么你就需要确定parse属性,parse属性是按照模板定义来解析一个字符串为 一个日期对象。如果bean中的属性是日期型对象则不需要添加parse属性。不论如何你都需要设置format属性。 format属性按你提供的模板对值进行格式化。
本示例中使用MM/dd/yyyy模板格式化日期型值。因为bean中的born属性值为字符串,所以我们需要 使用parse属性来将它转换成日期型数值。
<ec:column property="born" cell="date" parse="yyyy-MM-dd" format="MM/dd/yyyy"/>
对于货币只需要设置format属性:
<ec:column property="payroll" cell="currency" format="###,###,##0.00"/>
很多时候在extremeTable中,你使用同样的模版来解析和格式化日期和货币值。 所以便利的方法是在你自己的extremecomponents.properties文件中定义解析和格式化属性。 参阅Preferences章了解更多信息。
你可能记得在TableTag中看见过filterable和sortable属性,ColumnTag中也有相同的属性。 列的filterable和sortable属性将覆盖表的filterable和sortable属性设置。当你需要除了对表中的一、两列之外的 所有列进行过滤和排序时,十分便利。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="firstName" filterable="false"/>
<ec:column property="lastName" sortable="false"/>
</ec:row>
</ec:table>
列新增了两个属性:calc和calcTitle:
<ec:column property="data" calc="total" calcTitle="Total:" />
calc属性实现具有唯一方法的Calc接口:
public interface Calc {
public Number getCalcResult(TableModel model, Column column);
}
它传入model和column,并返回一个Number型的值。默认的实现为总计和平均值。
为了使用定制的Calc,只需要使用ColumnTag的calc属性来指定实现Calc接口的实现类的 全路径。
Calc为singleton并且不是线程安全的,因此不要定义任何类变量。
viewsAllowed属性制定类允许使用的视图。视图包括:html、pdf、xls、csv,以及任何定制的视图。 如果你指定一个或几个视图,那么列仅能使用这些指定的视图。例如:你指定viewsAllowed="pdf",这意味着 这列只允许PDF导出,而不能进行其他格式的导出或html视图。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="firstName"/>
<ec:column property="lastName" viewsAllowed="pdf"/>
</ec:row>
</ec:table>
viewsDenied属性制定类不允许使用的视图。视图包括:html、pdf、xls、csv,以及任何定制的视图。 如果你指定一个或几个视图,那么列仅这些指定的视图不能被使用。例如:你指定viewsDenied="html",这意味着 这列不允许使用html试图,但能进行任何形式的导出。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
>
<ec:row>
<ec:column property="firstName"/>
<ec:column property="lastName" viewsDenied="html"/>
</ec:row>
</ec:table>
title属性用来为header设定一个描述性的名称。如果你不定义title那么列将使用属性名。 如果你不想显示任何title,你只需要设置title属性值为一个空白(whitespace)。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
title="Presidents"
>
<ec:row>
<ec:column property="firstName"/> //title shows as First Name
<ec:column property="firstName" title="First Name"/> //title shows as First Name
<ec:column property="firstName" title=" "/> //no title shows up
</ec:row>
</ec:table>
大多数标签包含一系列的固定属性,这样那些已经实现的功能能够被使用。然而,eXtremeTable具有一种更具弹性的架构, 你可以添加自己的标签属性实现更多的定制工作。此外,eXtremeTable提供了非常清晰的钩子(hooks)允许你得到那些定制的 标签属性来做一些你需要的工作。
通过addExtendedAttributes()方法将扩展属性包含到eXtremeTable里:
public void addExtendedAttributes(Column column);
如果方法被覆盖ColumnTag将调用它。你需要做的就是扩展ColumnTag,覆盖addExtendedAttributes()方法,然后添加自己 的属性到列对象中。一个定制的CustomTag示例如下:
public class MyCustomTag extends ColumnTag {
private String customAttributeOne;
public String getCustomAttributeOne() {
return customAttributeOne;
}
public void setCustomAttributeOne(String customAttributeOne) {
this.customAttributeOne = customAttributeOne;
}
public void addExtendedAttributes(Column column) {
column.addAttribute("customAttributeOne", customAttributeOne);
}
}
现在你添加了属性值到Column对象,现在你可以像下例一样来定制cell:
public class MyCustomCell implements Cell {
public String getHtmlDisplay(TableModel model, Column column) {
Object customAttributeOne = column.getAttribute("customAttributeOne")
String customAttributeOne = column.getAttributeAsString("customAttributeOne")
}
}
另外,你也可以定制自己的标签和自己的TLD文件。你不需要修改extremecomponents.tld文件。 你能象使用eXtremeTable里的标签一样使用自己的标签,除了使用你自己标签的参照。假如你的标签参照为mycompany 并且标签为customColumn,你可以像下面一样使用他们:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/public/demo/presidents.jsp"
title="Presidents"
>
<ec:row>
<mycompany:customColumn
property="hello"
cell="com.mycompany.cell.MyCustomCell"
customAttributeOne="Hello World"/>
</ec:row>
</ec:table>
eXtremeTable具有导出不同格式文件的功能,导出的数据为过滤和排序后的所有结果集, 分页不会影响返回的结果集。换句话说,如果表数据分多页显示,那么所有页的数据都将被导出。 导出的格式为Microsoft Excel (OpenOffice Calc)、PDF和CSV。
使用ExportXlsTag导出Microsoft Excel (OpenOffice Calc):
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
/>
<ec:exportXls
fileName="presidents.xls"
tooltip="Export Excel"/>
...
</ec:table>
使用ExportPdfTag导出PDF。所有要做的就是指定fileName属性和一些样式属性:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
/>
<ec:exportPdf
fileName="presidents.pdf"
tooltip="Export PDF"
headerColor="blue"
headerBackgroundColor="red"
headerTitle="Presidents"/>
...
</ec:table>
使用ExportCsvTag导出CSV。当使用CSV导出是默认的分隔符为‘,’(comma)。你可以使用 delimiter属性来指定为其他的符号。下面为指定‘|’(pipe)为CSV分隔符的示例:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
/>
<ec:exportCsv
fileName="presidents.txt"
tooltip="Export CSV"
delimiter="|"/>
...
</ec:table>
你可以通过指定view属性来导出其他文件格式。eXtremeTable视图实现View接口并是 可插接的。参阅View章了解更多信息。
大多数标签包含一系列的固定属性,这样那些已经实现的功能能够被使用。然而,eXtremeTable具有一种更具弹性的架构, 你可以添加自己的标签属性实现更多的定制工作。此外,eXtremeTable提供了非常清晰的钩子(hooks)允许你得到那些定制的 标签属性来做一些你需要的工作。
通过addExtendedAttributes()方法将扩展属性包含到eXtremeTable里:
public void addExtendedAttributes(Export export);
如果方法被覆盖ExportTag将调用它。你需要做的就是扩展ExportTag,覆盖addExtendedAttributes()方法,然后添加自己 的属性到导出对象中。
一个定制的ExportCsvTag示例如下:
public class ExportCsvTag extends ExportTag {
private String delimiter;
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public void addExtendedAttributes(Export export) {
String view = export.getView();
if (StringUtils.isBlank(view)) {
export.setView(TableConstants.VIEW_CSV);
export.setImageName(TableConstants.VIEW_CSV);
}
export.addAttribute(CsvView.DELIMITER, getDelimiter());
}
}
现在你添加了属性值到Export对象,下面是CsvView实现的一部分:
public class CsvView implements View {
public void body(TableModel model, Column column) {
Export export = model.getExportHandler().getCurrentExport();
}
}
另外,你也可以定制自己的标签和自己的TLD文件。你不需要修改extremecomponents.tld文件。 你能象使用eXtremeTable里的标签一样使用自己的标签,除了使用你自己标签的参照。假如你的标签参照为mycompany 并且标签为customExport,你可以像下面一样使用他们:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/public/demo/presidents.jsp"
title="Presidents"
>
<mycompany:customExport fileName="presidents.txt" delimiter="|"/>
...
</ec:table>
Callbacks被用于重新得到(retrieve)、过滤和排序行数据。eXtremeTable为每个callback提供了一个定制实现。 首先,载入所有的元数据,元数据为所有eXtremeTable标签的所有属性;接着在eXtremeTable的model中调用 execute方法。eXtremeTable使用execute方法决定如何通过调用每个callback的接口来重新得到、过滤和排序行数据。 这三个callback的接口是:RetrieveRowsCallback、FilterRowsCallback和SortRowsCallback。
callbacks为singleton并且不是线程安全的,因此不要定义任何类变量。
8.2. RetrieveRowsCallback
RetrieveRowsCallback的默认实现在servlet范围内寻找具有名称和TableTag 的items属性设置相同的Beans集合。为了使用定制的callback,只要实现RetrieveRowsCallback接口, 然后使用retrieveRowsCallback属性来指定实现类的全路径:
<ec:table
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
retrieveRowsCallback="com.mycompany.callback.MyCustomCallback"
/>
RetrieveRowsCallback接口如下所示:
public interface RetrieveRowsCallback {
public Collection retrieveRows(TableModel model) throws Exception;
}
只有一个方法需要实现,传入TableModel并返回一个集合,集合为Beans或Maps集合。 通过得到TableModel,就拥有了TableTag的所有元数据并能访问Context。能够访问Context非常重要, 这意味着你访问web容器的任何东西。
FilterRowsCallback的默认实现得到Beans集合,通过实现jakarta Predicate接口进行过滤, 过滤值从eXtremeTable的filter输入框中取得。为了使用定制的callback,只要实现FilterRowsCallback接口, 然后使用filterRowsCallback属性来指定实现类的全路径:
<ec:table
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
filterRowsCallback="com.mycompany.callback.MyCustomCallback"
/>
FilterRowsCallback接口如下所示:
public interface FilterRowsCallback {
public Collection filterRows(TableModel model, Collection rows) throws Exception;
}
只有一个方法需要实现,传入TableModel并返回一个集合。你只需像eXtremeTable对 每个callback的默认实现一样来定制自己的callback。
SortRowsCallback的默认实现得到Beans集合,使用jakarta BeanComparator进行排序, 排序值当用户点击列头时取得。为了使用定制的callback,只要实现SortRowsCallback接口, 然后使用sortRowsCallback属性来指定实现类的全路径:
<ec:table
var="pres"
action="${pageContext.request.contextPath}/presidents.run"
sortRowsCallback="com.mycompany.callback.MyCustomCallback"
/>
SortRowsCallback接口如下所示:
public interface SortRowsCallback {
public Collection sortRows(TableModel model, Collection rows) throws Exception;
}
只有一个方法需要实现,传入TableModel并返回一个集合。你只需像eXtremeTable对 每个callback的默认实现一样来定制自己的callback。
为了替代硬编码eXtremeTable使用的默认属性值,我在属性文件中配置所有用到的属性。 如果你需要覆盖任何默认的设置,你可以创建自己的extremecomponents.properties文件 并设置你想改变的值。
为了设置属性文件,你应该如下例所示在/WEB-INF/web.xml文件中声明一个context-param,并 指定你的属性文件的路径:
<context-param>
<param-name>extremecomponentsPreferencesLocation</param-name>
<param-value>/org/extremesite/resource/extremecomponents.properties</param-value>
</context-param>
你可以认为属性文件为你提供了一个对所有的eXtremeTables声明全局设置的一个方法。 创建属性文件的最大好处就是避免在标签中复制、粘贴相同的属性。典型的extremecomponents.properties文件如下所示:
table.imagePath=/extremesite/images/*.gif
table.rowsDisplayed=12
column.parse.date=yyyy-MM-dd
column.format.date=MM/dd/yyyy
column.format.currency=$###,###,##0.00
在属性文件定义的TableTag使用最多的两个属性是:imagePath和rowsDisplayed。如果你不在属性文件中声明 这些属性,你需要在每个eXtremeTable中添加他们。典型的表如下所示:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
imagePath="${pageContext.request.contextPath}/images/*.gif"
rowsDisplayed="12"
title="Presidents"
>
...
</ec:table>
如果在属性文件声明imagePath和rowsDisplayed,则表如下所示:
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/presidents.run"
title="Presidents"
>
...
</ec:table>
正如你所见,属性文件避免了重复编码。
在属性文件定义的ColumnTag使用最多的两个属性是:parse和format。如果你不在属性文件中声明 这些属性,你需要在每个eXtremeTable中添加他们。典型的列使用日期cell如下所示:
<ec:column property="dateOfBirth" cell=”date” parse=”yyyy-MM-dd” format=”MM/dd/yyyy”/>
如果在属性文件声明parse和format,则列如下所示:
<ec:column property="dateOfBirth" cell=”date”/>
当然你仍然可以定义parse和format属性来覆盖全局设置,但是大多数工程对于日期使用一致的parse 和format。需要注意属性文件中parse.date和format.date的声明语法。
下例为使用货币cell的典型列:
<ec:column property="salary" cell=”currency” format=”$###,###,##0.00”/>
如果在属性文件声明format,则列如下所示:
<ec:column property="salary" cell=”currency”/>
另外,你可以声明一个定制的format并在列中通过使用列的basis来使用它,我把这想象为named属性。因此如果你的 extremecomponents.properties文件如下所示:
table.format.myCustomDate=yy-MM-dd
那么列可以如下使用定制的format:
<ec:column property="dateOfBirth" cell="date" format=”myCustomDate”>
10.4. Advanced Techniques
使用named属性是我定义其他不同属性默认值时经常使用的方法。你可能对我 使用cell="date"来指定日期cell、使用cell="currency"来指定货币cell或使用view="xls."来指定xls导出感到疑惑。 如果我给你展示extremetable.properties文件的一些片断,这些就将非常清晰了。 extremetable.properties是eXtremeTable声明默认设置的属性文件,你可以通过使用 extremecomponents.properties文件来覆盖它。
column.cell.date=org.extremecomponents.table.cell.DateCell
column.cell.currency=org.extremecomponents.table.cell.NumberCell
column.filterCell.droplist=org.extremecomponents.table.cell.FilterDroplistCell
table.view.xls=org.extremecomponents.table.view.XlsView
当你在列上定义cell="date"时,eXtremeTable寻找到column.cell. 属性并将你定义的cell属性值拼接上。 换句话说cell="date"关联到column.cell.date=org.extremecomponents.table.cell.DateCell这条属性。使用属性文件 真正强大的地方在于你能够在extremecomponents.properties文件中声明一个定制的cell,并在ColumnTag中通过 名称来使用它。
再使用一个实例来阐明这一点,是否记得ColumnTag章Cell节中如何调用一个名为MyCell的定制cell:
<ec:column property="firstName" cell="com.mycompany.cell.MyCell"/>
cell使用的更好方式是在属性文件中声明并通过名称使用它。首先,更新extremecomponents.properties文件:
table.imagePath=/extremesite/images/*.gif
table.rowsDisplayed=12
table.cellspacing=2
column.parse.date=yyyy-MM-dd
column.format.date=MM/dd/yyyy
column.format.currency=$###,###,##0.00
column.cell.myCell=com.mycompany.cell.MyCell
现在可以通过名称调用MyCell:
<ec:column property="firstName" cell="myCell"/>
正如你所见的这能帮助保持代码清洁,并且这些都在一个地方定义。如果你的定制cell声明 需要改变你只需要修改属性文件。
为了设置资源绑定,你应该如下例所示在/WEB-INF/web.xml文件中声明一个context-param,并 指定你的资源文件的路径:
<context-param>
<param-name>extremecomponentsMessagesLocation</param-name>
<param-value>org/extremesite/resource/extremecomponentsResourceBundle</param-value>
</context-param>
本示例中资源文件为extremecomponentsResourceBundle,它可以为任何名或者使用已经存在的资源文件。
如果你不指定locale,则它将根据你的servlet request来决定使用哪个资源文件。 在eXtremeTable中可以通过使用TableTag的locale属性来设置它。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/public/demo/locale.jsp"
title="table.title.president"
locale="de_DE"
>
...
</ec:table>
在这里eXtremeTable将寻找德文资源文件。
eXtremeTable使用一些全局的keys来与用户交互,包括:状态栏的文本信息,Rows Displayed droplist和不同的tooltips。如果你足够幸运,eXtremeTable已经提供了相应的语言支持 的话,那么你什么也不用担心。否则的话,你需要申明下列keys:
statusbar.resultsFound={0} results found, displaying {1} to {2}
statusbar.noResultsFound=There were no results found.
toolbar.firstPageTooltip=First Page
toolbar.lastPageTooltip=Last Page
toolbar.prevPageTooltip=Previous Page
toolbar.nextPageTooltip=Next Page
toolbar.filterTooltip=Filter
toolbar.clearTooltip=Clear
toolbar.clearText=Clear
toolbar.firstPageText=First
toolbar.lastPageText=Last
toolbar.nextPageText=Next
toolbar.prevPageText=Prev
toolbar.filterText=Filter
column.headercell.sortTooltip=Sort By
column.calc.total=Total
column.calc.average=Average
现在仅支持英语和德语。如果你使用其他语言的话,并能提供相应的翻译的话我将不胜感激。你可以通过 extremecomponents@gmail.com发送给我。
译者注:我已经提供了中文和日文的资源文件。
TableTag属性中能够使用locale方式指定的是:imagePath和title。
在eXtremeTable中,imagePath属性有一个特定的key:table.imagePath。你可以在你的资源文件中 设置这个key为特定语言的目录结构。例如:德文图片可能放在de文件夹下,那么你可以在相应的资源文件中 进行如下设置:
table.imagePath=/extremesite/images/table/de/*.gif
title有一点不同,如果你指定的title属性值包含dot (.)并且你定义了一个资源文件,那么 eXtremeTable将寻找匹配的key。例如,如果你像下例一样在表中指定属性title="table.title.president":
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/public/demo/locale.jsp"
title="table.title.president"
>
...
</ec:table>
那么eXtremeTable将在属性文件中寻找匹配的key:
table.title.president=US Präsidenten
ColumnTag属性中能够使用locale方式指定的是:format和title。
在eXtremeTable中,format属性有一个特定的key:table.fomat.type。参考属性文件的讨论 来了解更多的细节,他们具有同样的概念。日期和货币的format类型定义可能如下所示:
column.format.date=MM/dd/yyyy
column.format.currency=$###,###,##0.00
title有一点不同,如果你指定的title属性值包含dot (.)并且你定义了一个资源文件,那么 eXtremeTable将寻找匹配的key。例如,如果你像下例一样在列中指定属性title="table.column.nickName":
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/public/demo/locale.jsp"
title="table.title.president"
>
<ec:row>
<ec:column property="nickName" title="table.column.nickName" />
</ec:row>
</ec:table>
那么eXtremeTable将在属性文件中寻找匹配的key:
table.column.nickName=Spitzname
默认的情况下eXtremeTable取得所有的结果集然后处理Beans集合,这样的好处是 你可以随意进行排序、过滤和分页操作。你只需要组装Beans集合并让eXtremeTable知道如何 引用它。这样的操作对于小到中等数据量的结果集非常有效,当结果集很大时这将非常糟糕。 这是一个判断,但我更喜欢描述如何做出我的技术决定。如果您认为在性能上有问题, 那么最好是使用一个profiler工具记录并查看它。有许多开源和商业的profiler工具可以帮助 你做出最好的判断。因此,假设我们发现了性能上存在问题,需要我们自己来处理分页。
手动处理分页意味着你一次只想取得一页显示需要的结果集。同时,你需要自己处理排序、过滤和分页。 下面的讨论是基于我假设你从数据库中取得集合,当然同样的原理能应用到任何地方。
这是一个重要的部分。为了得到较小的结果集,你可以创建一个普通的查询语句,但是limit你得到的结果集。 在Sybase和SQLServer中你可以使用rowcount命令,在MySql中你可以使用limit命令。 我不知道其他数据库怎么使用,但我确信每个数据库都有相似的功能。 也就是说当用户浏览第一页是得到第一页需要的 结果集,当用户浏览下一页时,再得到下一页需要的结果集。
使用Sybase的开发人员可能会说:rowcount命令总是从第一条开始,那么当我到第二页时我也必须 从第一条数据开始。 是的,你现在得到的是两页的结果集,而不需要得到所有的结果集。当你到第三页时,你只需要得到三页的结果集。。。。。。 其他数据库比如MySQL,允许你精确地得到你想要的那段数据,这样你就可以只得到当前页面显示需要的结果集。
为了知道用户想如何排序和过滤,他们想浏览哪一页,一页需要显示几条结果,eXtremeTable有一个使用LimitFactory 创建的名为Limit的简便接口:
首先你需要通过LimitFactory得到一个Limit实例:
Context context = new HttpServletRequestContext(request);
LimitFactory limitFactory = new TableLimitFactory(context, tableId);
Limit limit = new TableLimit(limitFactory);
Limit对象定义了limit结果集的所有方法。
TableLimitFactory具有另外一个构造函数,如果没有指定tableId的话默认的tableId将为ec。
Context context = new HttpServletRequestContext(request);
LimitFactory limitFactory = new TableLimitFactory(context);
Limit limit = new TableLimit(limitFactory);
当你对Limit实例化时,实例化对象包含两个对象:FilterSet和Sort。
private FilterSet filterSet;
private Sort sort;
FilterSet包含一个过滤动作(Action)和一个过滤器对象数组。 动作为TableConstants.FILTER_ACTION或TableConstants.CLEAR_ACTION。 一个过滤器包含一个property和这个过滤器的值。
private final String action;
private final Filter[] filters;
Sort对象包含property和sortOrder。sortOrder为 TableConstants.SORT_ASC或TableConstants.SORT_DESC:
private Sort sort;
设置行属性:
limit.setRowAttributes(totalRows, DEFAULT_ROWS_DISPLAYED);
下面是设置行属性可能用到的信息:
private int rowStart;
private int rowEnd;
private int currentRowsDisplayed;
private int page;
private int totalRows;
每个变量都有一个getter方法,我将不深入讲解属性的细节。
在你完成所有的定制工作:排序、过滤.....定制的Controller(Spring)或者Action(Struts)或者其他类似的框架后, 另外你需要创建一个callback,eXtremeTable已经提供了一个名为LimitCallback的实现。为了使用你只需要设置表 属性:retrieveRowsCallback、filterRowsCallback和sortRowsCallback:
<ec:table
items="presidents"
retrieveRowsCallback="limit"
filterRowsCallback="limit"
sortRowsCallback="limit"
action="${pageContext.request.contextPath}/limit.run"
title="Presidents"
>
<ec:row>
<ec:column property="fullName" title="Name"/>
<ec:column property="nickName" />
<ec:column property="term" />
<ec:column property="born" cell="date"/>
<ec:column property="died" cell="date"/>
<ec:column property="career" />
</ec:row>
</ec:table>
使用callback需要做的唯一事情是传输集合到request,同时传输totalRows属性。 totalRows表示总行数,使用PaginationCallback.TOTAL_ROWS静态变量将易于维护。 如果JSP页面使用了两个(以上)eXtremeTable的话你可以利用tableId分别传输totalRows。 例如如果tableId名为pres,你可以如下处理:
request.setAttribute("pres", presidents);
request.setAttribute("pres_totalRows", new Integer(""+totalRows));
译者注:关于limit使用的更详细信息,请参考《Limit指南》。
Chapter 13. AutoGenerateColumns
大多数情况下你按照你需要的列来设计数据库表。但是,有时候需要运行时动态生成一些列。 为了实现这点,eXtremeTable需要使用ColumnsTag并设置autoGenerateColumns属性。
AutoGenerateColumns为singleton并且不是线程安全的,因此不要定义任何类变量。
ColumnsTag只有autoGenerateColumns这一个属性。所有你必须做的就是实现AutoGenerateColumns接口, 并设置autoGenerateColumns属性为类的全路径。
<ec:table
items="presidents"
action="${pageContext.request.contextPath}/autoGenerateColumns.run"
title="Presidents"
>
<ec:columns autoGenerateColumns="org.extremesite.controller.AutoGenerateColumnsImpl"/>
</ec:table>
AutoGenerateColumns接口只有一个方法:
public void addColumns(TableModel model);
你需要做的就是添加列(columns)到model里。最简单的示例如下:
public class AutoGenerateColumnsImpl implements AutoGenerateColumns {
public void addColumns(TableModel model) {
Iterator iterator = columnsToAdd().iterator();
while (iterator.hasNext()) {
Map columnToAdd = (Map) iterator.next();
Column column = new Column(model);
column.setProperty((String) columnToAdd.get(PROPERTY));
column.setCell((String) columnToAdd.get(CELL));
model.getColumnHandler().addAutoGenerateColumn(column);
}
}
}
示例中columnsToAdd()方法简单返回一个包含生成列(columns)需要的所有信息的集合。 作为参考,下面是我在eXtremeComponents网站实例中使用的columnsToAdd()方法:
private List columnsToAdd() {
List columns = new ArrayList();
columns.add(columnToAdd("fullName", "display"));
columns.add(columnToAdd("nickName", "display"));
columns.add(columnToAdd("term", "display"));
columns.add(columnToAdd("born", "date"));
columns.add(columnToAdd("died", "date"));
columns.add(columnToAdd("career", "display"));
return columns;
}
private Map columnToAdd(String property, String cell) {
Map column = new HashMap();
column.put(Column.PROPERTY, property);
column.put(Column.CELL, cell);
return column;
}
另外,我想声明的是只创建列一次。eXtremeTable为了高效,不会每行创建一列, 而是通过循环持续插入新列值到已经存在的列。记住TableModel能够访问Context,因此 你可以在Controller(Spring)或Action(Struts)中定义样式(look like)并通过request传输集合。 所以你得AutoGenerateColumns实现只需要构建列(Columns)并添加到model.columns里。