eXtremeComponents(简称ec)是一系列提供高级显示的开源JSP定制标签,当前的包含的组件为eXtremeTable,用于以表形式显示数据。
其本质是jsp的自定义标签,抓住这一点就抓住了ec的本源。
1. Table定义
我们先看一下标签的定义:extremComponents.tld,其中table的标签定义如下:
<tag> <name>table</name> <tag-class>org.extremecomponents.table.tag.TableTag</tag-class> <body-content>JSP</body-content> <display-name>TableTag</display-name> <description><![CDATA[The container which holds all the main table information. Will also hold global information if needed. The table tag is copied into the Table and encapsulated in the Model.]]></description> <attribute> <name>action</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The URI that will be called when the filter, sort and pagination is used.]]></description> </attribute> <attribute> <name>autoIncludeParameters</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not to automatically include the parameters, as hidden inputs, passed into the JSP.]]></description> </attribute> <attribute> <name>border</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The table border attribute. The default is 0.]]></description> </attribute> <attribute> <name>bufferView</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Whether of not to buffer the view. Boolean value with the default being false.]]></description> </attribute> <attribute> <name>cellpadding</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The table cellpadding attribute. The default is 0.]]></description> </attribute> <attribute> <name>cellspacing</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The table cellspacing attribute. The default is 0.]]></description> </attribute> <attribute> <name>filterable</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not the table is filterable. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>filterRowsCallback</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[A fully qualified class name to a custom FilterRowsCallback implementation. Could also be a named type in the preferences. Used to filter the Collection of Beans or Collection of Maps.]]></description> </attribute> <attribute> <name>form</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The reference to a surrounding form element.]]></description> </attribute> <attribute> <name>imagePath</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The path to find the images. For example imagePath=/extremesite/images/*.png is saying look in the image directory for the .png images.]]></description> </attribute> <attribute> <name>interceptor</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[A fully qualified class name to a custom InterceptTable implementation. Could also be a named type in the preferences. Used to add table attributes.]]></description> </attribute> <attribute> <name>items</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Reference the collection that will be retrieved.]]></description> </attribute> <attribute> <name>locale</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The locale for this table. For example fr_FR is used for the French translation.]]></description> </attribute> <attribute> <name>method</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Used to invoke the table action using a POST or GET.]]></description> </attribute> <attribute> <name>onInvokeAction</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The javascript that will be invoked when a table action enabled.]]></description> </attribute> <attribute> <name>retrieveRowsCallback</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[A fully qualified class name to a custom RetrieveRowsCallback implementation. Could also be a named type in the preferences. Used to retrieve the Collection of Beans or Collection of Maps.]]></description> </attribute> <attribute> <name>rowsDisplayed</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The number of rows to display in the table.]]></description> </attribute> <attribute> <name>scope</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The scope (page, request, session, or application) to find the Collection of beans or Collection of Maps defined by the collection attribute.]]></description> </attribute> <attribute> <name>showPagination</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not the table should use pagination. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>showExports</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not the table should use the exports. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>showStatusBar</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not the table should use the status bar. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>showTitle</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not to show the title. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>showTooltips</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not to show the tooltips. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>sortRowsCallback</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[A fully qualified class name to a custom SortRowsCallback implementation. Could also be a named type in the preferences. Used to sort the Collection of Beans or Collection of Maps.]]></description> </attribute> <attribute> <name>sortable</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Specify whether or not the table is sortable. Boolean value with the default being true.]]></description> </attribute> <attribute> <name>state</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The table state to use when returning to a table. Acceptable values are default, notifyToDefault, persist, notifyToPersist.]]></description> </attribute> <attribute> <name>stateAttr</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The table attribute used to invoke the state change of the table.]]></description> </attribute> <attribute> <name>style</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The css inline style sheet.]]></description> </attribute> <attribute> <name>styleClass</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The css class style sheet.]]></description> </attribute> <attribute> <name>tableId</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The unique identifier for the table.]]></description> </attribute> <attribute> <name>theme</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The theme to style the table. The default is eXtremeTable.]]></description> </attribute> <attribute> <name>title</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The title of the table. The title will display above the table.]]></description> </attribute> <attribute> <name>var</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[The name of the variable to hold the current row bean.]]></description> </attribute> <attribute> <name>view</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Generates the output. The default is the HtmlView to generate the HTML. Also used by the exports to generate XLS-FO, POI, and CSV.]]></description> </attribute> <attribute> <name>width</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[Width of the table.]]></description> </attribute> </tag>
从上面我们可以看到,先定义了table标签的实现类:org.extremecomponents.table.tag.TableTag,其作用是:
The container which holds all the main table information. Will also hold global information if needed. The table tag is copied into the Table and encapsulated in the Model.
一句话:保存table的信息及一些全局信息,通过table封装到Model中。
然后定义了table的一些属性,这些属性和org.extremecomponents.table.bean.Table一致。
public class Table extends Attributes { private TableModel model; private String action; private Boolean autoIncludeParameters; private String border; private Boolean bufferView; private String cellpadding; private String cellspacing; private Boolean filterable; private String filterRowsCallback; private String form; private String imagePath; private String interceptor; private Object items; private String locale; private int maxRowsDisplayed; private int medianRowsDisplayed; private String method; private String onInvokeAction; private String retrieveRowsCallback; private int rowsDisplayed; private Boolean saveFilterSort; private String scope; private Boolean showExports; private Boolean showPagination; private Boolean showStatusBar; private Boolean showTitle; private Boolean showTooltips; private String sortRowsCallback; private Boolean sortable; private String state; private String stateAttr; private String style; private String styleClass; private String tableId; private String title; private String theme; private String var; private String view; private String width; public Table(TableModel model) { this.model = model; } // setter and getter }
2. TableTag实现
我们来看一下TableTage如何实现table标签?首先:TableTage继承了TagSupport:
web容器执行自定义标签的过程如下
初始化时调用setPageContent()方法,完了在调用setParent方法
2.1 web容器首先执行自定义标签的开始标记,同时调用doStartTag方法。
2.2 如果doStartTag方法返回EVAL_BODY_INCLUDE,web容器在执行完标签体的内容后,会调用标签类得doAfterBody方法;
如果doStartTag方法返回SKIP_BODY,doAfterBody方法不会调用,web容器会直接调用标签类得doEndTag方法
2.3 如果doAfterBody方法被调用,并且方法返回EVAL_BODY_AGIN,web容器会再次执行标签体的内容;
如果doAfterBody方法返回SKIP_BODY,web容器会调用标签类的doEndTag方法
2.4 如果doEndTag方法返回EVAL_PAGE,web容器会执行标签后面的内容;
如果doEndTag方法返回SKIP_PAGE,web容器会忽略自定义标签后面的内容
public int doStartTag() throws JspException { try { // initialize the attributes iterator = null; pageContext.setAttribute(TableConstants.ROWCOUNT, "0"); // fire up the model with the context, preferences and messages model = new TableModelImpl(new JspPageContext(pageContext), TagUtils.evaluateExpressionAsString("locale", this.locale, this, pageContext)); // make the table Table table = new Table(model); table.setAction(TagUtils.evaluateExpressionAsString("action", action, this, pageContext)); table.setAutoIncludeParameters(TagUtils.evaluateExpressionAsBoolean("autoIncludeParameters", this.autoIncludeParameters, this, pageContext)); table.setBorder(TagUtils.evaluateExpressionAsString("border", this.border, this, pageContext)); table.setBufferView(TagUtils.evaluateExpressionAsBoolean("bufferView", this.bufferView, this, pageContext)); table.setCellpadding(TagUtils.evaluateExpressionAsString("cellpadding", this.cellpadding, this, pageContext)); table.setCellspacing(TagUtils.evaluateExpressionAsString("cellspacing", this.cellspacing, this, pageContext)); table.setFilterable(TagUtils.evaluateExpressionAsBoolean("filterable", this.filterable, this, pageContext)); table.setFilterRowsCallback(TagUtils.evaluateExpressionAsString("filterRowsCallback", this.filterRowsCallback, this, pageContext)); table.setForm(TagUtils.evaluateExpressionAsString("form", this.form, this, pageContext)); table.setImagePath(TagUtils.evaluateExpressionAsString("imagePath", this.imagePath, this, pageContext)); table.setInterceptor(TagUtils.evaluateExpressionAsString("interceptor", this.interceptor, this, pageContext)); table.setItems(TagUtils.evaluateExpressionAsObject("items", this.items, this, pageContext)); table.setLocale(TagUtils.evaluateExpressionAsString("locale", this.locale, this, pageContext)); table.setMethod(TagUtils.evaluateExpressionAsString("method", this.method, this, pageContext)); table.setOnInvokeAction(TagUtils.evaluateExpressionAsString("onInvokeAction", this.onInvokeAction, this, pageContext)); table.setRetrieveRowsCallback(TagUtils.evaluateExpressionAsString("retrieveRowsCallback", this.retrieveRowsCallback, this, pageContext)); table.setRowsDisplayed(TagUtils.evaluateExpressionAsInt("rowsDisplayed", this.rowsDisplayed, this, pageContext)); table.setScope(TagUtils.evaluateExpressionAsString("scope", scope, this, pageContext)); table.setShowExports(TagUtils.evaluateExpressionAsBoolean("showExports", this.showExports, this, pageContext)); table.setShowPagination(TagUtils.evaluateExpressionAsBoolean("showPagination", this.showPagination, this, pageContext)); table.setShowStatusBar(TagUtils.evaluateExpressionAsBoolean("showStatusBar", this.showStatusBar, this, pageContext)); table.setShowTitle(TagUtils.evaluateExpressionAsBoolean("showTitle", this.showTitle, this, pageContext)); table.setShowTooltips(TagUtils.evaluateExpressionAsBoolean("showTooltips", this.showTooltips, this, pageContext)); table.setSortRowsCallback(TagUtils.evaluateExpressionAsString("sortRowsCallback", this.sortRowsCallback, this, pageContext)); table.setSortable(TagUtils.evaluateExpressionAsBoolean("sortable", this.sortable, this, pageContext)); table.setState(TagUtils.evaluateExpressionAsString("state", this.state, this, pageContext)); table.setStateAttr(TagUtils.evaluateExpressionAsString("stateAttr", this.stateAttr, this, pageContext)); table.setStyle(TagUtils.evaluateExpressionAsString("style", style, this, pageContext)); table.setStyleClass(TagUtils.evaluateExpressionAsString("styleClass", this.styleClass, this, pageContext)); table.setTableId(TagUtils.evaluateExpressionAsString("tableId", tableId, this, pageContext)); table.setTheme(TagUtils.evaluateExpressionAsString("theme", this.theme, this, pageContext)); table.setTitle(TagUtils.evaluateExpressionAsString("title", this.title, this, pageContext)); table.setVar(TagUtils.evaluateExpressionAsString("var", this.var, this, pageContext)); table.setView(TagUtils.evaluateExpressionAsString("view", this.view, this, pageContext)); table.setWidth(TagUtils.evaluateExpressionAsString("width", this.width, this, pageContext)); addTableAttributes(model, table); model.addTable(table); } catch (Exception e) { throw new JspException("TableTag.doStartTag() Problem: " + ExceptionUtils.formatStackTrace(e)); } return EVAL_BODY_INCLUDE; } /** * Two things need to be accomplished here. First, need to iterate once over * the columns to load up all the attributes. Second, need to iterate over * the columns as many times as specified by the rowsDisplayed attribute so * the columns row can be resolved. On each iteration over the columns the * current bean in the collection is passed via the pageScope. */ public int doAfterBody() throws JspException { try { if (iterator == null) { iterator = model.execute().iterator(); } if (iterator != null && iterator.hasNext()) { Object bean = iterator.next(); model.setCurrentRowBean(bean); return EVAL_BODY_AGAIN; } } catch (Exception e) { throw new JspException("TableTag.doAfterBody() Problem: " + ExceptionUtils.formatStackTrace(e)); } return SKIP_BODY; } public int doEndTag() throws JspException { try { pageContext.getOut().println(model.getViewData()); } catch (Exception e) { throw new JspException("TableTag.doEndTag() Problem: " + ExceptionUtils.formatStackTrace(e)); } return EVAL_PAGE; }
3. 深入追踪到TableModel
在doAfterBody()方法中调用了TableModel的excute方法:
public Collection execute() throws Exception {
//1. 查询记录 Collection rows = TableModelUtils.retrieveRows(this); rows = new ArrayList(rows); // copy for thread safety this.collectionOfBeans = rows; //2. 过滤和排序记录 rows = TableModelUtils.filterRows(this, rows); rows = TableModelUtils.sortRows(this, rows); this.collectionOfFilteredBeans = rows; // 3. 获取记录总数和pagesize Integer totalRows = getTableHandler().getTotalRows();
int defaultRowsDisplayed = getTableHandler().getTable().getRowsDisplayed(); if (totalRows != null) {
//4. 设置记录总数和默认pagesize limit.setRowAttributes(totalRows.intValue(), defaultRowsDisplayed); } else { limit.setRowAttributes(rows.size(), defaultRowsDisplayed); } if (logger.isDebugEnabled()) { logger.debug(limit.toString()); } //5. 获取本次显示的记录 rows = TableModelUtils.getCurrentRows(this, rows); this.collectionOfPageBeans = rows; //6. 视图显示 viewHandler.setView(); return rows; }
4. 视图显示
默认定义了三种视图模式:extremetable.properties
table.view.compact=org.extremecomponents.table.view.CompactView
table.view.limit=org.extremecomponents.table.view.LimitView
table.view.html=org.extremecomponents.table.view.HtmlView
public void setView() throws Exception { boolean isExported = model.getLimit().isExported(); String currentView = null; if (isExported) { currentView = model.getExportHandler().getCurrentExport().getView(); String preference = model.getPreferences().getPreference(PreferencesConstants.EXPORT_VIEW + currentView); if (StringUtils.isNotBlank(preference)) { currentView = preference; } } else { currentView = model.getTableHandler().getTable().getView(); String preference = model.getPreferences().getPreference(PreferencesConstants.TABLE_VIEW + currentView); if (StringUtils.isNotBlank(preference)) { currentView = preference; } } Class classDefinition = Class.forName(currentView); this.view = (View) classDefinition.newInstance(); getView().beforeBody(model); }
调用抽象类的beforeBody方法:
public final void beforeBody(TableModel model) { this.model = model; bufferView = model.getTableHandler().getTable().isBufferView(); if (bufferView) { html = new HtmlBuilder(); } else { html = new HtmlBuilder(model.getContext().getWriter()); } formBuilder = new FormBuilder(html, model); init(html, model); formBuilder.formStart(); tableBuilder.themeStart(); beforeBodyInternal(model); }
调用HtmlView的beforeBodyInternal处理
protected void beforeBodyInternal(TableModel model) { toolbar(getHtmlBuilder(), getTableModel()); getTableBuilder().tableStart(); getTableBuilder().theadStart(); statusBar(getHtmlBuilder(), getTableModel()); getTableBuilder().filterRow(); getTableBuilder().headerRow(); getTableBuilder().theadEnd(); getTableBuilder().tbodyStart(); }
工具栏:
protected void toolbar(HtmlBuilder html, TableModel model) {
//layout 布局 new DefaultToolbar(html, model).layout(); }
左右布局格式:
public void layout() { if (!showLayout(model)) { return; } html.table(0).border("0").cellPadding("0").cellSpacing("0"); Table table = model.getTableHandler().getTable(); html.width(table.getWidth()).close(); html.tr(1).close(); // layout area left columnLeft(html, model); // layout area right columnRight(html, model); html.trEnd(1); html.tableEnd(0); html.newline(); }
布局的实现:
protected void columnLeft(HtmlBuilder html, TableModel model) { html.td(2).close(); new TableBuilder(html, model).title(); html.tdEnd(); } protected void columnRight(HtmlBuilder html, TableModel model) { boolean showPagination = BuilderUtils.showPagination(model); boolean showExports = BuilderUtils.showExports(model); ToolbarBuilder toolbarBuilder = new ToolbarBuilder(html, model); html.td(2).align("right").close(); html.table(2).border("0").cellPadding("0").cellSpacing("1").styleClass(BuilderConstants.TOOLBAR_CSS).close(); html.tr(3).close(); if (showPagination) { html.td(4).close(); toolbarBuilder.firstPageItemAsImage(); html.tdEnd(); html.td(4).close(); toolbarBuilder.prevPageItemAsImage(); html.tdEnd(); html.td(4).close(); toolbarBuilder.nextPageItemAsImage(); html.tdEnd(); html.td(4).close(); toolbarBuilder.lastPageItemAsImage(); html.tdEnd(); html.td(4).close(); toolbarBuilder.separator(); html.tdEnd(); html.td(4).style("width:20px").close(); html.newline(); html.tabs(4); toolbarBuilder.rowsDisplayedDroplist(); html.img(); html.src(BuilderUtils.getImage(model, BuilderConstants.TOOLBAR_ROWS_DISPLAYED_IMAGE)); html.style("border:0"); html.alt("Rows Displayed"); html.xclose(); html.tdEnd(); if (showExports) { html.td(4).close(); toolbarBuilder.separator(); html.tdEnd(); } } if (showExports) { Iterator iterator = model.getExportHandler().getExports().iterator(); for (Iterator iter = iterator; iter.hasNext();) { html.td(4).close(); Export export = (Export) iter.next(); toolbarBuilder.exportItemAsImage(export); html.tdEnd(); } } html.trEnd(3); html.tableEnd(2); html.newline(); html.tabs(2); html.tdEnd(); }
状态栏和工具栏类似,就不一一赘述了。
5. 动作触发
以下一页来理解ectable中是如何触发事件的:
public void nextPageItemAsImage() {
ImageItem item = new ImageItem();
item.setTooltip(messages.getMessage(BuilderConstants.TOOLBAR_NEXT_PAGE_TOOLTIP));
item.setDisabledImage(BuilderUtils.getImage(model, BuilderConstants.TOOLBAR_NEXT_PAGE_DISABLED_IMAGE));
item.setImage(BuilderUtils.getImage(model, BuilderConstants.TOOLBAR_NEXT_PAGE_IMAGE));
item.setAlt(messages.getMessage(BuilderConstants.TOOLBAR_NEXT_PAGE_TEXT));
item.setStyle("border:0");
ToolbarItemUtils.buildNextPage(html, model, item);
}
下一页的构建
public static void buildNextPage(HtmlBuilder html, TableModel model, ToolbarItem item) { int page = model.getLimit().getPage(); String action = new TableActions(model).getPageAction(page + 1); item.setAction(action); int totalPages = BuilderUtils.getTotalPages(model); if (!BuilderUtils.isNextPageEnabled(page, totalPages)) { item.disabled(html); } else { item.enabled(html, model); } }
触发动作,js实现
public String getPageAction(int page) { StringBuffer action = new StringBuffer("javascript:"); action.append(getClearedExportTableIdParameters()); action.append(getFormParameter(TableConstants.PAGE, "" + page)); action.append(getOnInvokeAction()); return action.toString(); }
一个完整的table示例如下:
<table border="0" cellpadding="0" cellspacing="0" width="60%" > <tr> <td><span class="title" >persons</span></td> <td align="right" > <table border="0" cellpadding="0" cellspacing="1" class="toolbar" > <tr> <td><img src="/address-book/public/images/table/firstPageDisabled.gif" style="border:0" alt="第一页" /></td> <td><img src="/address-book/public/images/table/prevPageDisabled.gif" style="border:0" alt="上一页" /></td> <td><img src="/address-book/public/images/table/nextPageDisabled.gif" style="border:0" alt="下一页" /></td> <td><img src="/address-book/public/images/table/lastPageDisabled.gif" style="border:0" alt="最后页" /></td> <td><img src="/address-book/public/images/table/separator.gif" style="border:0" alt="Separator" /></td> <td style="width:20px" > <select name="ec_rd" onchange="javascript:document.forms.ec.ec_crd.value=this.options[this.selectedIndex].value;document.forms.ec.ec_p.value='1';document.forms.ec.setAttribute('action','/address-book/person/list');document.forms.ec.setAttribute('method','post');document.forms.ec.submit()" > <option value="5" selected="selected">5</option><option value="50" >50</option><option value="100" >100</option> </select><img src="/address-book/public/images/table/rowsDisplayed.gif" style="border:0" alt="Rows Displayed" /></td> </tr> </table> </td> </tr> </table>
6. 小结:
eXtremeComponents为jsp开发table提供了比较好的支持,它的源码可以作为学习jsp tag的一个很好示例,研究该源码可以加深对jsp的理解,通过对上述源码进行合适的扩展后可以作为组件库使用。
参考文献:
1. http://liuna718-163-com.iteye.com/blog/1318991
2. http://www.51cto.com/specbook/11/57761.htm