Integrating Peng's recent API changes to CellTableBuilder into HeaderCreator. HeaderCreator now includes both the rendering code and the event handling logic, eliminating the HeaderCreator.Helper class completely. This allows for a simpler implementation of HeaderCreator that only supports a single row, or an application specific implementation, either of which could be lighter weight than the default implementation. The API is very similar to CellTableBuilder, but slightly simpler because there is no concept of row values in a header. Note that there are no behavioral changes here, its just a rearrangement of API.
The one big question, which this change does not answer, is which class to rename: HeaderCreator or CellTableBuilder. Personally, I think that both classes are much more builder-like than they were, so maybe we can just rename HeaderCreator to HeaderBuilder. That would certainly be the easiest from an adoption standpoint, as CellTableBuilder already has wide use and would be harder to rename.
Also, we could drop the "Cell" from CellTableBuilder to make it more consistent with HeaderBuilder/Creator. However, that would be a pain, so I'm happy just leaving CellTableBuilder as is.
Review at http://gwt-code-reviews.appspot.com/1533804
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10597 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
index b69b8b3..a52f3ba 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
@@ -49,14 +49,13 @@
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
+import com.google.gwt.user.cellview.client.AbstractHeaderOrFooterBuilder;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.ColumnSortList;
import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
import com.google.gwt.user.cellview.client.DataGrid;
-import com.google.gwt.user.cellview.client.DefaultHeaderCreator;
import com.google.gwt.user.cellview.client.Header;
-import com.google.gwt.user.cellview.client.HeaderCreator;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.cellview.client.TextHeader;
@@ -149,7 +148,7 @@
* address of the contacts grouped under the "Information" category.
*/
@ShowcaseSource
- private class CustomHeaderCreator extends DefaultHeaderCreator<ContactInfo> {
+ private class CustomHeaderBuilder extends AbstractHeaderOrFooterBuilder<ContactInfo> {
private Header<String> firstNameHeader = new TextHeader(constants
.cwCustomDataGridColumnFirstName());
@@ -161,18 +160,18 @@
private Header<String> addressHeader =
new TextHeader(constants.cwCustomDataGridColumnAddress());
- public CustomHeaderCreator() {
+ public CustomHeaderBuilder() {
super(dataGrid, false);
setSortIconStartOfLine(false);
}
@Override
- public void buildHeader(Helper<ContactInfo> helper) {
+ protected boolean buildHeaderOrFooterImpl() {
Style style = dataGrid.getResources().style();
String groupHeaderCell = resources.styles().groupHeaderCell();
// Add a 2x2 header above the checkbox and show friends columns.
- TableRowBuilder tr = helper.startRow();
+ TableRowBuilder tr = startRow();
tr.startTH().colSpan(2).rowSpan(2)
.className(style.header() + " " + style.firstColumnHeader());
tr.endTH();
@@ -182,7 +181,7 @@
* the group header sorts by last name.
*/
TableCellBuilder th = tr.startTH().colSpan(2).className(groupHeaderCell);
- helper.enableColumnHandlers(th, lastNameColumn);
+ enableColumnHandlers(th, lastNameColumn);
th.style().trustedProperty("border-right", "10px solid white").cursor(Cursor.POINTER)
.endStyle();
th.text("Name").endTH();
@@ -198,23 +197,20 @@
boolean isSortAscending = (sortedInfo == null) ? false : sortedInfo.isAscending();
// Add column headers.
- tr = helper.startRow();
- buildHeader(helper, tr, firstNameHeader, firstNameColumn, sortedColumn, isSortAscending,
- false, false);
- buildHeader(helper, tr, lastNameHeader, lastNameColumn, sortedColumn, isSortAscending, false,
- false);
- buildHeader(helper, tr, ageHeader, ageColumn, sortedColumn, isSortAscending, false, false);
- buildHeader(helper, tr, categoryHeader, categoryColumn, sortedColumn, isSortAscending, false,
- false);
- buildHeader(helper, tr, addressHeader, addressColumn, sortedColumn, isSortAscending, false,
- true);
+ tr = startRow();
+ buildHeader(tr, firstNameHeader, firstNameColumn, sortedColumn, isSortAscending, false, false);
+ buildHeader(tr, lastNameHeader, lastNameColumn, sortedColumn, isSortAscending, false, false);
+ buildHeader(tr, ageHeader, ageColumn, sortedColumn, isSortAscending, false, false);
+ buildHeader(tr, categoryHeader, categoryColumn, sortedColumn, isSortAscending, false, false);
+ buildHeader(tr, addressHeader, addressColumn, sortedColumn, isSortAscending, false, true);
tr.endTR();
+
+ return true;
}
/**
* Renders the header of one column, with the given options.
*
- * @param helper the helper used to build the header
* @param out the table row to build into
* @param header the {@link Header} to render
* @param column the column to associate with the header
@@ -223,9 +219,8 @@
* @param isFirst true if this the first column
* @param isLast true if this the last column
*/
- private void buildHeader(Helper<ContactInfo> helper, TableRowBuilder out, Header<?> header,
- Column<ContactInfo, ?> column, Column<?, ?> sortedColumn, boolean isSortAscending,
- boolean isFirst, boolean isLast) {
+ private void buildHeader(TableRowBuilder out, Header<?> header, Column<ContactInfo, ?> column,
+ Column<?, ?> sortedColumn, boolean isSortAscending, boolean isFirst, boolean isLast) {
// Choose the classes to include with the element.
Style style = dataGrid.getResources().style();
boolean isSorted = (sortedColumn == column);
@@ -248,11 +243,11 @@
TableCellBuilder th = out.startTH().className(classesBuilder.toString());
// Associate the cell with the column to enable sorting of the column.
- helper.enableColumnHandlers(th, column);
+ enableColumnHandlers(th, column);
// Render the header.
Context context = new Context(0, 2, header.getKey());
- renderHeader(th, context, header, helper, isSorted, isSortAscending);
+ renderSortableHeader(th, context, header, isSorted, isSortAscending);
// End the table cell.
th.endTH();
@@ -266,10 +261,14 @@
* changes with the row data in the table.
*/
@ShowcaseSource
- private class CustomFooterCreator implements HeaderCreator<ContactInfo> {
+ private class CustomFooterBuilder extends AbstractHeaderOrFooterBuilder<ContactInfo> {
+
+ public CustomFooterBuilder() {
+ super(dataGrid, true);
+ }
@Override
- public void buildHeader(HeaderCreator.Helper<ContactInfo> helper) {
+ protected boolean buildHeaderOrFooterImpl() {
String footerStyle = dataGrid.getResources().style().footer();
// Calculate the age of all visible contacts.
@@ -284,7 +283,7 @@
}
// Cells before age column.
- TableRowBuilder tr = helper.startRow();
+ TableRowBuilder tr = startRow();
tr.startTH().colSpan(4).className(footerStyle).endTH();
// Show the average age of all contacts.
@@ -297,6 +296,8 @@
// Cells after age column.
tr.startTH().colSpan(2).className(footerStyle).endTH();
tr.endTR();
+
+ return true;
}
}
@@ -337,16 +338,6 @@
// Display information about the user in another row that spans the entire
// table.
- if (rowValue.getAge() > 65) {
- TableRowBuilder row = startRow();
- TableCellBuilder td = row.startTD().colSpan(7).className(cellStyle);
- td.style().trustedBackgroundColor("#ffa").outlineStyle(OutlineStyle.NONE).endStyle();
- td.text(rowValue.getFirstName() + " is elegible for retirement benefits").endTD();
- row.endTR();
- }
-
- // Display information about the user in another row that spans the entire
- // table.
Date dob = rowValue.getBirthday();
if (dob.getMonth() == todayMonth) {
TableRowBuilder row = startRow();
@@ -623,8 +614,8 @@
// Specify a custom table.
dataGrid.setTableBuilder(new CustomTableBuilder());
- dataGrid.setHeaderCreator(new CustomHeaderCreator());
- dataGrid.setFooterCreator(new CustomFooterCreator());
+ dataGrid.setHeaderBuilder(new CustomHeaderBuilder());
+ dataGrid.setFooterBuilder(new CustomFooterBuilder());
// Add the CellList to the adapter in the database.
ContactDatabase.get().addDataDisplay(dataGrid);
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
index f942f2d..4e8cc6c 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
@@ -22,10 +22,7 @@
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
import com.google.gwt.dom.builder.shared.HtmlTableSectionBuilder;
-import com.google.gwt.dom.builder.shared.TableRowBuilder;
import com.google.gwt.dom.builder.shared.TableSectionBuilder;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
@@ -415,98 +412,6 @@
}
/**
- * Implementation of {@link HeaderCreator.Helper}.
- */
- private class HeaderHelperImpl extends HeaderCreator.Helper<T> {
-
- private final Map<String, Column<T, ?>> columnMap;
- private final TwoWayHashMap<String, Header<?>> headerMap;
- private boolean isEmpty = true;
- private final HtmlTableSectionBuilder section;
- private final String tag;
-
- public HeaderHelperImpl(AbstractCellTable<T> cellTable, boolean isFooter) {
- super(cellTable);
- if (isFooter) {
- section = HtmlBuilderFactory.get().createTFootBuilder();
- tag = "tfoot";
- headerMap = idToFooterMap;
- columnMap = idToFooterColumnMap;
- } else {
- section = HtmlBuilderFactory.get().createTHeadBuilder();
- tag = "thead";
- headerMap = idToHeaderMap;
- columnMap = idToHeaderColumnMap;
- }
- }
-
- @Override
- public void enableColumnHandlers(ElementBuilderBase<?> builder, Column<T, ?> column) {
- String columnId = "column-" + Document.get().createUniqueId();
- columnMap.put(columnId, column);
- builder.attribute(COLUMN_ATTRIBUTE, columnId);
- }
-
- @Override
- public <H> void renderHeader(ElementBuilderBase<?> out, Context context, Header<H> header) {
- // Generate a unique ID for the header.
- String headerId = headerMap.getKey(header);
- if (headerId == null) {
- headerId = "header-" + Document.get().createUniqueId();
- headerMap.put(headerId, header);
- }
- out.attribute(HEADER_ATTRIBUTE, headerId);
-
- // Render the cell into the builder.
- SafeHtmlBuilder sb = new SafeHtmlBuilder();
- header.render(context, sb);
- out.html(sb.toSafeHtml());
- }
-
- @Override
- public TableRowBuilder startRow() {
- isEmpty = false;
-
- // End any dangling rows.
- while (section.getDepth() > 1) {
- section.end();
- }
-
- // Verify the depth.
- if (section.getDepth() < 1) {
- throw new IllegalStateException(
- "Cannot start a row. Did you call TableRowBuilder.end() too many times?");
- }
-
- // Start the next row.
- TableRowBuilder row = section.startTR();
- return row;
- }
-
- /**
- * Get the {@link TableSectionElement} containing the children.
- */
- private SafeHtml asSafeHtml() {
- // Strip the table section tags off of the tbody.
- String rawHtml = section.asSafeHtml().asString();
- assert (tag.length()) == 5 : "Unrecognized tag: " + tag;
- assert rawHtml.startsWith("<" + tag + ">") : "Malformed html";
- assert rawHtml.endsWith("</" + tag + ">") : "Malformed html";
- rawHtml = rawHtml.substring(7, rawHtml.length() - 8);
- return SafeHtmlUtils.fromTrustedString(rawHtml);
- }
-
- /**
- * Check if the header is empty.
- *
- * @return true if no rows were created, false if not empty
- */
- private boolean isEmpty() {
- return isEmpty;
- }
- }
-
- /**
* Implementation of {@link AbstractCellTable}.
*/
private static class Impl {
@@ -626,22 +531,21 @@
// Remove all children in the range.
final int absEndIndex = table.getPageStart() + startIndex + childCount;
boolean done = false;
- Element insertBefore = table.getChildElement(startIndex);
+ TableRowElement insertBefore = table.getChildElement(startIndex).cast();
if (table.legacyRenderRowValues) {
int count = 0;
while (insertBefore != null && count < childCount) {
Element next = insertBefore.getNextSiblingElement();
section.removeChild(insertBefore);
- insertBefore = next;
+ insertBefore = (next == null) ? null : next.<TableRowElement> cast();
count++;
}
} else {
while (insertBefore != null
- && table.tableBuilder.getRowValueIndex(insertBefore.<TableRowElement> cast()) <
- absEndIndex) {
+ && table.tableBuilder.getRowValueIndex(insertBefore) < absEndIndex) {
Element next = insertBefore.getNextSiblingElement();
section.removeChild(insertBefore);
- insertBefore = next;
+ insertBefore = (next == null) ? null : next.<TableRowElement> cast();
}
}
@@ -782,41 +686,12 @@
}
/**
- * A map that provides O(1) access to a value given the key, or to the key
- * given the value.
+ * The error message used when {@link HeaderBuilder} returns malformed table
+ * section HTML.
*/
- private static class TwoWayHashMap<K, V> {
- private final Map<K, V> keyToValue = new HashMap<K, V>();
- private final Map<V, K> valueToKey = new HashMap<V, K>();
-
- void clear() {
- keyToValue.clear();
- valueToKey.clear();
- }
-
- K getKey(V value) {
- return valueToKey.get(value);
- }
-
- V getValue(K key) {
- return keyToValue.get(key);
- }
-
- void put(K key, V value) {
- keyToValue.put(key, value);
- valueToKey.put(value, key);
- }
- }
-
- /**
- * The attribute used to indicate that an element contains a Column.
- */
- private static final String COLUMN_ATTRIBUTE = "__gwt_column";
-
- /**
- * The attribute used to indicate that an element contains a header.
- */
- private static final String HEADER_ATTRIBUTE = "__gwt_header";
+ private static final String MALFORMED_HTML_SECTION =
+ "Malformed HTML: The table section returned by HeaderBuilder or FooterBuilder must use the "
+ + "tag name thead or tfoot, as appropriate, and cannot contain any attributes or styles.";
/*
* The table specific {@link Impl}.
@@ -834,14 +709,24 @@
}
/**
- * A mapping of unique cell IDs to the cell.
+ * Get the {@link TableSectionElement} containing the children.
+ *
+ * @param tag the expected tag (tbody, tfoot, or thead)
*/
- private final Map<String, Column<T, ?>> idToFooterColumnMap = new HashMap<String, Column<T, ?>>();
- private final TwoWayHashMap<String, Header<?>> idToFooterMap =
- new TwoWayHashMap<String, Header<?>>();
- private final Map<String, Column<T, ?>> idToHeaderColumnMap = new HashMap<String, Column<T, ?>>();
- private final TwoWayHashMap<String, Header<?>> idToHeaderMap =
- new TwoWayHashMap<String, Header<?>>();
+ private static SafeHtml tableSectionToSafeHtml(TableSectionBuilder section, String tag) {
+ if (!(section instanceof HtmlTableSectionBuilder)) {
+ throw new IllegalArgumentException("Only HtmlTableSectionBuilder is supported at this time");
+ }
+
+ // Strip the table section tags off of the tbody.
+ HtmlTableSectionBuilder htmlSection = (HtmlTableSectionBuilder) section;
+ String rawHtml = htmlSection.asSafeHtml().asString();
+ assert (tag.length()) == 5 : "Unrecognized tag: " + tag;
+ assert rawHtml.startsWith("<" + tag + ">") : MALFORMED_HTML_SECTION;
+ assert rawHtml.endsWith("</" + tag + ">") : MALFORMED_HTML_SECTION;
+ rawHtml = rawHtml.substring(7, rawHtml.length() - 8);
+ return SafeHtmlUtils.fromTrustedString(rawHtml);
+ }
private boolean cellIsEditing;
private final List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
@@ -860,7 +745,7 @@
private boolean dependsOnSelection;
private Widget emptyTableWidget;
- private HeaderCreator<T> footerCreator;
+ private FooterBuilder<T> footerBuilder;
private boolean footerRefreshDisabled;
private final List<Header<?>> footers = new ArrayList<Header<?>>();
@@ -869,7 +754,7 @@
*/
private boolean handlesSelection;
- private HeaderCreator<T> headerCreator;
+ private HeaderBuilder<T> headerBuilder;
private boolean headerRefreshDisabled;
private final List<Header<?>> headers = new ArrayList<Header<?>>();
@@ -1166,10 +1051,10 @@
}
/**
- * Get the {@link HeaderCreator} used to generate the footer section.
+ * Get the {@link HeaderBuilder} used to generate the footer section.
*/
- public HeaderCreator<T> getFooterCreator() {
- return footerCreator;
+ public FooterBuilder<T> getFooterBuilder() {
+ return footerBuilder;
}
/**
@@ -1181,10 +1066,10 @@
}
/**
- * Get the {@link HeaderCreator} used to generate the header section.
+ * Get the {@link HeaderBuilder} used to generate the header section.
*/
- public HeaderCreator<T> getHeaderCreator() {
- return headerCreator;
+ public HeaderBuilder<T> getHeaderBuilder() {
+ return headerBuilder;
}
/**
@@ -1554,22 +1439,22 @@
}
/**
- * Set the {@link HeaderCreator} used to build the footer section of the
+ * Set the {@link HeaderBuilder} used to build the footer section of the
* table.
*/
- public void setFooterCreator(HeaderCreator<T> creator) {
- assert creator != null : "creator cannot be null";
- this.footerCreator = creator;
+ public void setFooterBuilder(FooterBuilder<T> builder) {
+ assert builder != null : "builder cannot be null";
+ this.footerBuilder = builder;
redrawFooters();
}
/**
- * Set the {@link HeaderCreator} used to build the header section of the
+ * Set the {@link HeaderBuilder} used to build the header section of the
* table.
*/
- public void setHeaderCreator(HeaderCreator<T> creator) {
- assert creator != null : "creator cannot be null";
- this.headerCreator = creator;
+ public void setHeaderBuilder(HeaderBuilder<T> builder) {
+ assert builder != null : "builder cannot be null";
+ this.headerBuilder = builder;
redrawHeaders();
}
@@ -1732,7 +1617,7 @@
protected abstract TableSectionElement getTableFootElement();
/**
- * Get the thead element that contains theh eaders.
+ * Get the thead element that contains the headers.
*/
protected abstract TableSectionElement getTableHeadElement();
@@ -1768,8 +1653,10 @@
TableSectionElement targetTableSection = null;
TableCellElement targetTableCell = null;
Element cellParent = null;
- String columnId = null;
- String headerId = null;
+ Element headerParent = null; // Header in the headerBuilder.
+ Element headerColumnParent = null; // Column in the headerBuilder.
+ Element footerParent = null; // Header in the footerBuilder.
+ Element footerColumnParent = null; // Column in the footerBuilder.
{
Element maybeTableCell = null;
Element cur = target;
@@ -1797,26 +1684,31 @@
maybeTableCell = cur;
}
- // Look for the most immediate associated column if not already found.
- if (columnId == null) {
- String curColumnId = cur.getAttribute(COLUMN_ATTRIBUTE);
- if (curColumnId != null && curColumnId.length() > 0) {
- columnId = curColumnId;
- }
+ // Look for the most immediate cell parent if not already found.
+ if (cellParent == null && tableBuilder.isColumn(cur)) {
+ cellParent = cur;
}
- if (cellParent == null) {
- // Look for the most immediate cell parent if not already found.
- if (tableBuilder.isColumn(cur)) {
- cellParent = cur;
- }
+ /*
+ * Look for the most immediate header parent if not already found. Its
+ * possible that the footer or header will mistakenly identify a header
+ * from the other section, so we remember both. When we eventually reach
+ * the target table section element, we'll know for sure if its a header
+ * of footer.
+ */
+ if (headerParent == null && headerBuilder.isHeader(cur)) {
+ headerParent = cur;
+ }
+ if (footerParent == null && footerBuilder.isHeader(cur)) {
+ footerParent = cur;
+ }
- // Look for the most immediate header parent if not already found.
- String curHeaderId = isHeaderParent(cur);
- if (curHeaderId != null) {
- headerId = curHeaderId;
- cellParent = cur;
- }
+ // Look for the most immediate column parent if not already found.
+ if (headerColumnParent == null && headerBuilder.isColumn(cur)) {
+ headerColumnParent = cur;
+ }
+ if (footerColumnParent == null && footerBuilder.isColumn(cur)) {
+ footerColumnParent = cur;
}
// Iterate.
@@ -1842,11 +1734,14 @@
int col = targetTableCell.getCellIndex();
if (targetTableSection == thead || targetTableSection == tfoot) {
boolean isHeader = (targetTableSection == thead);
+ headerParent = isHeader ? headerParent : footerParent;
+ Element columnParent = isHeader ? headerColumnParent : footerColumnParent;
// Fire the event to the header.
- TwoWayHashMap<String, Header<?>> headerMap = isHeader ? idToHeaderMap : idToFooterMap;
- if (headerId != null) {
- Header<?> header = headerMap.getValue(headerId);
+ if (headerParent != null) {
+ Header<?> header =
+ isHeader ? headerBuilder.getHeader(headerParent) : footerBuilder
+ .getHeader(footerParent);
if (header != null && cellConsumesEventType(header.getCell(), eventType)) {
Context context = new Context(0, col, header.getKey());
header.onBrowserEvent(context, cellParent, event);
@@ -1854,9 +1749,10 @@
}
// Sort the header.
- Map<String, Column<T, ?>> columnMap = isHeader ? idToHeaderColumnMap : idToFooterColumnMap;
- if (isClick) {
- Column<T, ?> column = columnMap.get(columnId);
+ if (isClick && columnParent != null) {
+ Column<T, ?> column =
+ isHeader ? headerBuilder.getColumn(columnParent) : footerBuilder
+ .getColumn(columnParent);
if (column != null && column.isSortable()) {
/*
* Force the headers to refresh the next time data is pushed so we
@@ -2285,16 +2181,7 @@
// Update the properties of the table.
coalesceCellProperties();
TableSectionBuilder tableSectionBuilder = tableBuilder.finish();
- if (tableSectionBuilder instanceof HtmlTableSectionBuilder) {
- // Strip the table section tags off of the tbody.
- String rawHtml = ((HtmlTableSectionBuilder) tableSectionBuilder).asSafeHtml().asString();
- assert rawHtml.startsWith("<tbody>") : "Malformed html";
- assert rawHtml.endsWith("</tbody>") : "Malformed html";
- rawHtml = rawHtml.substring(7, rawHtml.length() - 8);
- return SafeHtmlUtils.fromTrustedString(rawHtml);
- } else {
- throw new IllegalStateException("Only HTML table section builder is supported at this time");
- }
+ return tableSectionToSafeHtml(tableSectionBuilder, "tbody");
}
/**
@@ -2337,21 +2224,16 @@
* @param isFooter true if this is the footer table, false if the header table
*/
private void createHeaders(boolean isFooter) {
- HeaderHelperImpl helper = new HeaderHelperImpl(this, isFooter);
- if (isFooter) {
- idToFooterMap.clear();
- idToFooterColumnMap.clear();
- footerCreator.buildHeader(helper);
- TABLE_IMPL.replaceAllRows(this, getTableFootElement(), helper.asSafeHtml());
+ TableSectionBuilder section =
+ isFooter ? footerBuilder.buildFooter() : headerBuilder.buildHeader();
+ if (section != null) {
+ TABLE_IMPL.replaceAllRows(this, isFooter ? getTableFootElement() : getTableHeadElement(),
+ tableSectionToSafeHtml(section, isFooter ? "tfoot" : "thead"));
+ doSetHeaderVisible(isFooter, true);
} else {
- idToHeaderMap.clear();
- idToHeaderColumnMap.clear();
- headerCreator.buildHeader(helper);
- TABLE_IMPL.replaceAllRows(this, getTableHeadElement(), helper.asSafeHtml());
+ // If the section isn't used, hide it.
+ doSetHeaderVisible(isFooter, false);
}
-
- // If the section isn't used, hide it.
- doSetHeaderVisible(isFooter, !helper.isEmpty());
}
/**
@@ -2481,25 +2363,14 @@
// Set the table builder.
tableBuilder = new DefaultCellTableBuilder<T>(this);
- headerCreator = new DefaultHeaderCreator<T>(this, false);
- footerCreator = new DefaultHeaderCreator<T>(this, true);
+ headerBuilder = new DefaultHeaderOrFooterBuilder<T>(this, false);
+ footerBuilder = new DefaultHeaderOrFooterBuilder<T>(this, true);
// Set the keyboard handler.
setKeyboardSelectionHandler(new CellTableKeyboardSelectionHandler<T>(this));
}
/**
- * Check if an element is the parent of a rendered header.
- *
- * @param elem the element to check
- * @return the headerId if a cell parent, null if not
- */
- private String isHeaderParent(Element elem) {
- String headerId = elem.getAttribute(HEADER_ATTRIBUTE);
- return (headerId == null) || (headerId.length() == 0) ? null : headerId;
- }
-
- /**
* Mark the column widths as dirty and redraw the table.
*/
private void refreshColumnsAndRedraw() {
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractHeaderOrFooterBuilder.java b/user/src/com/google/gwt/user/cellview/client/AbstractHeaderOrFooterBuilder.java
new file mode 100644
index 0000000..fdec154
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractHeaderOrFooterBuilder.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.cellview.client;
+
+import com.google.gwt.cell.client.Cell.Context;
+import com.google.gwt.dom.builder.shared.DivBuilder;
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
+import com.google.gwt.dom.builder.shared.HtmlTableSectionBuilder;
+import com.google.gwt.dom.builder.shared.StylesBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default implementation of {@link HeaderBuilder} that renders columns.
+ *
+ * @param <T> the data type of the table
+ */
+public abstract class AbstractHeaderOrFooterBuilder<T> implements HeaderBuilder<T>,
+ FooterBuilder<T> {
+
+ /**
+ * A map that provides O(1) access to a value given the key, or to the key
+ * given the value.
+ */
+ private static class TwoWayHashMap<K, V> {
+ private final Map<K, V> keyToValue = new HashMap<K, V>();
+ private final Map<V, K> valueToKey = new HashMap<V, K>();
+
+ void clear() {
+ keyToValue.clear();
+ valueToKey.clear();
+ }
+
+ K getKey(V value) {
+ return valueToKey.get(value);
+ }
+
+ V getValue(K key) {
+ return keyToValue.get(key);
+ }
+
+ void put(K key, V value) {
+ keyToValue.put(key, value);
+ valueToKey.put(value, key);
+ }
+ }
+
+ /**
+ * The attribute used to indicate that an element contains a Column.
+ */
+ private static final String COLUMN_ATTRIBUTE = "__gwt_column";
+
+ /**
+ * The attribute used to indicate that an element contains a header.
+ */
+ private static final String HEADER_ATTRIBUTE = "__gwt_header";
+
+ private static final int ICON_PADDING = 6;
+
+ private final boolean isFooter;
+ private boolean isSortIconStartOfLine = true;
+ private final int sortAscIconHalfHeight;
+ private SafeHtml sortAscIconHtml;
+ private final int sortAscIconWidth;
+ private final int sortDescIconHalfHeight;
+ private SafeHtml sortDescIconHtml;
+ private final int sortDescIconWidth;
+ private final AbstractCellTable<T> table;
+
+ // The following fields are reset on every build.
+ private HtmlTableSectionBuilder section;
+ private final Map<String, Column<T, ?>> idToColumnMap = new HashMap<String, Column<T, ?>>();
+ private final TwoWayHashMap<String, Header<?>> idToHeaderMap =
+ new TwoWayHashMap<String, Header<?>>();
+
+ /**
+ * Create a new DefaultHeaderBuilder for the header of footer section.
+ *
+ * @param table the table being built
+ * @param isFooter true if building the footer, false if the header
+ */
+ public AbstractHeaderOrFooterBuilder(AbstractCellTable<T> table, boolean isFooter) {
+ this.isFooter = isFooter;
+ this.table = table;
+
+ /*
+ * Cache the height and width of the sort icons. We do not cache the
+ * rendered image source so the compiler can optimize it out if the user
+ * overrides renderHeader and does not use the sort icon.
+ */
+ ImageResource asc = table.getResources().sortAscending();
+ ImageResource desc = table.getResources().sortDescending();
+ if (asc != null) {
+ sortAscIconWidth = asc.getWidth() + ICON_PADDING;
+ sortAscIconHalfHeight = (int) Math.round(asc.getHeight() / 2.0);
+ } else {
+ sortAscIconWidth = 0;
+ sortAscIconHalfHeight = 0;
+ }
+ if (desc != null) {
+ sortDescIconWidth = desc.getWidth() + ICON_PADDING;
+ sortDescIconHalfHeight = (int) Math.round(desc.getHeight() / 2.0);
+ } else {
+ sortDescIconWidth = 0;
+ sortDescIconHalfHeight = 0;
+ }
+ }
+
+ @Override
+ public final TableSectionBuilder buildFooter() {
+ if (!isFooter) {
+ throw new UnsupportedOperationException(
+ "Cannot build footer because this builder is designated to build a header");
+ }
+ return buildHeaderOrFooter();
+ }
+
+ @Override
+ public final TableSectionBuilder buildHeader() {
+ if (isFooter) {
+ throw new UnsupportedOperationException(
+ "Cannot build header because this builder is designated to build a footer");
+ }
+ return buildHeaderOrFooter();
+ }
+
+ @Override
+ public Column<T, ?> getColumn(Element elem) {
+ String cellId = getColumnId(elem);
+ return (cellId == null) ? null : idToColumnMap.get(cellId);
+ }
+
+ @Override
+ public Header<?> getHeader(Element elem) {
+ String headerId = getHeaderId(elem);
+ return (headerId == null) ? null : idToHeaderMap.getValue(headerId);
+ }
+
+ /**
+ * Check if this builder is building a header or footer table.
+ *
+ * @return true if a footer, false if a header
+ */
+ public boolean isBuildingFooter() {
+ return isFooter;
+ }
+
+ @Override
+ public boolean isColumn(Element elem) {
+ return getColumnId(elem) != null;
+ }
+
+ @Override
+ public boolean isHeader(Element elem) {
+ return getHeaderId(elem) != null;
+ }
+
+ /**
+ * Check if the icon is located at the start or end of the line. The start of
+ * the line refers to the left side in LTR mode and the right side in RTL
+ * mode. The default location is the start of the line.
+ */
+ public boolean isSortIconStartOfLine() {
+ return isSortIconStartOfLine;
+ }
+
+ /**
+ * Set the position of the sort icon to the start or end of the line. The
+ * start of the line refers to the left side in LTR mode and the right side in
+ * RTL mode. The default location is the start of the line.
+ */
+ public void setSortIconStartOfLine(boolean isStartOfLine) {
+ this.isSortIconStartOfLine = isStartOfLine;
+ }
+
+ /**
+ * Implementation that builds the header or footer using the convenience
+ * methods in this class.
+ *
+ * @return true if the header contains content, false if empty
+ */
+ protected abstract boolean buildHeaderOrFooterImpl();
+
+ /**
+ * Enables column-specific event handling for the specified element. If a
+ * column is sortable, then clicking on the element or a child of the element
+ * will trigger a sort event.
+ *
+ * @param builder the builder to associate with the column. The builder should
+ * be a child element of a row returned by {@link #startRow} and must
+ * be in a state where an attribute can be added.
+ * @param column the column to associate
+ */
+ protected final void enableColumnHandlers(ElementBuilderBase<?> builder, Column<T, ?> column) {
+ String columnId = "column-" + Document.get().createUniqueId();
+ idToColumnMap.put(columnId, column);
+ builder.attribute(COLUMN_ATTRIBUTE, columnId);
+ }
+
+ /**
+ * Get the header or footer at the specified index.
+ *
+ * @param index the column index of the header
+ * @return the header or footer, depending on the value of isFooter
+ */
+ protected final Header<?> getHeader(int index) {
+ return isFooter ? getTable().getFooter(index) : getTable().getHeader(index);
+ }
+
+ protected AbstractCellTable<T> getTable() {
+ return table;
+ }
+
+ /**
+ * Renders a given Header into a given ElementBuilderBase. This method ensures
+ * that the CellTable widget will handle events events originating in the
+ * Header.
+ *
+ * @param <H> the data type of the header
+ * @param out the {@link ElementBuilderBase} to render into. The builder
+ * should be a child element of a row returned by {@link #startRow}
+ * and must be in a state that allows both attributes and elements to
+ * be added
+ * @param context the {@link Context} of the header being rendered
+ * @param header the {@link Header} to render
+ */
+ protected final <H> void renderHeader(ElementBuilderBase<?> out, Context context, Header<H> header) {
+ // Generate a unique ID for the header.
+ String headerId = idToHeaderMap.getKey(header);
+ if (headerId == null) {
+ headerId = "header-" + Document.get().createUniqueId();
+ idToHeaderMap.put(headerId, header);
+ }
+ out.attribute(HEADER_ATTRIBUTE, headerId);
+
+ // Render the cell into the builder.
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ header.render(context, sb);
+ out.html(sb.toSafeHtml());
+ }
+
+ /**
+ * Render a header, including a sort icon if the column is sortable and
+ * sorted.
+ *
+ * @param out the builder to render into
+ * @param header the header to render
+ * @param context the context of the header
+ * @param isSorted true if the column is sorted
+ * @param isSortAscending indicated the sort order, if sorted
+ */
+ protected final void renderSortableHeader(ElementBuilderBase<?> out, Context context,
+ Header<?> header, boolean isSorted, boolean isSortAscending) {
+ ElementBuilderBase<?> headerContainer = out;
+
+ // Wrap the header in a sort icon if sorted.
+ isSorted = isSorted && !isFooter;
+ if (isSorted) {
+ // Determine the position of the sort icon.
+ boolean posRight =
+ LocaleInfo.getCurrentLocale().isRTL() ? isSortIconStartOfLine : !isSortIconStartOfLine;
+
+ // Create an outer container to hold the icon and the header.
+ int iconWidth = isSortAscending ? sortAscIconWidth : sortDescIconWidth;
+ int halfHeight = isSortAscending ? sortAscIconHalfHeight : sortDescIconHalfHeight;
+ DivBuilder outerDiv = out.startDiv();
+ StylesBuilder style =
+ outerDiv.style().position(Position.RELATIVE).trustedProperty("zoom", "1");
+ if (posRight) {
+ style.paddingRight(iconWidth, Unit.PX);
+ } else {
+ style.paddingLeft(iconWidth, Unit.PX);
+ }
+ style.endStyle();
+
+ // Add the icon.
+ DivBuilder imageHolder = outerDiv.startDiv();
+ style =
+ outerDiv.style().position(Position.ABSOLUTE).top(50.0, Unit.PCT).lineHeight(0.0, Unit.PX)
+ .marginTop(-halfHeight, Unit.PX);
+ if (posRight) {
+ style.right(0, Unit.PX);
+ } else {
+ style.left(0, Unit.PX);
+ }
+
+ style.endStyle();
+ imageHolder.html(getSortIcon(isSortAscending));
+ imageHolder.endDiv();
+
+ // Create the header wrapper.
+ headerContainer = outerDiv.startDiv();
+ }
+
+ // Build the header.
+ renderHeader(headerContainer, context, header);
+
+ // Close the elements used for the sort icon.
+ if (isSorted) {
+ headerContainer.endDiv(); // headerContainer.
+ headerContainer.endDiv(); // outerDiv
+ }
+ }
+
+ /**
+ * Add a header (or footer) row to the table, below any rows previously added.
+ *
+ * @return the row to add
+ */
+ protected final TableRowBuilder startRow() {
+ // End any dangling rows.
+ while (section.getDepth() > 1) {
+ section.end();
+ }
+
+ // Verify the depth.
+ if (section.getDepth() < 1) {
+ throw new IllegalStateException(
+ "Cannot start a row. Did you call TableRowBuilder.end() too many times?");
+ }
+
+ // Start the next row.
+ TableRowBuilder row = section.startTR();
+ return row;
+ }
+
+ private TableSectionBuilder buildHeaderOrFooter() {
+ // Reset the state of the header.
+ section =
+ isFooter ? HtmlBuilderFactory.get().createTFootBuilder() : HtmlBuilderFactory.get()
+ .createTHeadBuilder();
+ idToHeaderMap.clear();
+ idToColumnMap.clear();
+
+ // Build the header.
+ if (!buildHeaderOrFooterImpl()) {
+ // The header is empty.
+ return null;
+ }
+
+ // End dangling elements.
+ while (section.getDepth() > 0) {
+ section.end();
+ }
+
+ // Return the section.
+ return section;
+ }
+
+ /**
+ * Check if an element is the parent of a rendered header.
+ *
+ * @param elem the element to check
+ * @return the id if a header parent, null if not
+ */
+ private String getColumnId(Element elem) {
+ return getElementAttribute(elem, COLUMN_ATTRIBUTE);
+ }
+
+ private String getElementAttribute(Element elem, String attribute) {
+ if (elem == null) {
+ return null;
+ }
+ String value = elem.getAttribute(attribute);
+ return (value == null) || (value.length() == 0) ? null : value;
+ }
+
+ /**
+ * Check if an element is the parent of a rendered header.
+ *
+ * @param elem the element to check
+ * @return the id if a header parent, null if not
+ */
+ private String getHeaderId(Element elem) {
+ return getElementAttribute(elem, HEADER_ATTRIBUTE);
+ }
+
+ /**
+ * Get the HTML representation of the sort icon. These are loaded lazily so
+ * the compiler has a chance to strip this method, and the icon source code,
+ * if the user overrides renderHeader.
+ *
+ * @param isAscending true for the ascending icon, false for descending
+ * @return the rendered HTML
+ */
+ private SafeHtml getSortIcon(boolean isAscending) {
+ if (isAscending) {
+ if (sortAscIconHtml == null) {
+ AbstractImagePrototype proto =
+ AbstractImagePrototype.create(table.getResources().sortAscending());
+ sortAscIconHtml = SafeHtmlUtils.fromTrustedString(proto.getHTML());
+ }
+ return sortAscIconHtml;
+ } else {
+ if (sortDescIconHtml == null) {
+ AbstractImagePrototype proto =
+ AbstractImagePrototype.create(table.getResources().sortDescending());
+ sortDescIconHtml = SafeHtmlUtils.fromTrustedString(proto.getHTML());
+ }
+ return sortDescIconHtml;
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java b/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
index 00cb2e2..0fdf0ed 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
@@ -33,7 +33,6 @@
*
* @param <T> the row data type
*/
-// TODO(jlabanca): Rename to CellTableCreator
public interface CellTableBuilder<T> {
/**
diff --git a/user/src/com/google/gwt/user/cellview/client/DefaultHeaderCreator.java b/user/src/com/google/gwt/user/cellview/client/DefaultHeaderCreator.java
deleted file mode 100644
index e2dc6e7..0000000
--- a/user/src/com/google/gwt/user/cellview/client/DefaultHeaderCreator.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.user.cellview.client;
-
-import com.google.gwt.cell.client.Cell.Context;
-import com.google.gwt.dom.builder.shared.DivBuilder;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.StylesBuilder;
-import com.google.gwt.dom.builder.shared.TableCellBuilder;
-import com.google.gwt.dom.builder.shared.TableRowBuilder;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.safehtml.shared.SafeHtml;
-import com.google.gwt.safehtml.shared.SafeHtmlUtils;
-import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
-import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
-
-/**
- * Default implementation of {@link HeaderCreator} that renders columns.
- *
- * <p>
- * WARNING: This API is experimental and may change without warning.
- * </p>
- *
- * @param <T> the data type of the table
- */
-public class DefaultHeaderCreator<T> implements HeaderCreator<T> {
-
- private static final int ICON_PADDING = 6;
-
- private final boolean isFooter;
- private boolean isSortIconStartOfLine = true;
- private final int sortAscIconHalfHeight;
- private SafeHtml sortAscIconHtml;
- private final int sortAscIconWidth;
- private final int sortDescIconHalfHeight;
- private SafeHtml sortDescIconHtml;
- private final int sortDescIconWidth;
- private final AbstractCellTable<T> table;
-
- /**
- * Create a new DefaultHeaderBuilder for the header of footer section.
- *
- * @param table the table being built
- * @param isFooter true if building the footer, false if the header
- */
- public DefaultHeaderCreator(AbstractCellTable<T> table, boolean isFooter) {
- this.isFooter = isFooter;
- this.table = table;
-
- /*
- * Cache the height and width of the sort icons. We do not cache the
- * rendered image source so the compiler can optimize it out if the user
- * overrides renderHeader and does not use the sort icon.
- */
- ImageResource asc = table.getResources().sortAscending();
- ImageResource desc = table.getResources().sortDescending();
- if (asc != null) {
- sortAscIconWidth = asc.getWidth() + ICON_PADDING;
- sortAscIconHalfHeight = (int) Math.round(asc.getHeight() / 2.0);
- } else {
- sortAscIconWidth = 0;
- sortAscIconHalfHeight = 0;
- }
- if (desc != null) {
- sortDescIconWidth = desc.getWidth() + ICON_PADDING;
- sortDescIconHalfHeight = (int) Math.round(desc.getHeight() / 2.0);
- } else {
- sortDescIconWidth = 0;
- sortDescIconHalfHeight = 0;
- }
- }
-
- @Override
- public void buildHeader(Helper<T> utility) {
- // Early exit if there aren't any columns to render.
- int columnCount = table.getColumnCount();
- if (columnCount == 0) {
- // Nothing to render;
- return;
- }
-
- // Early exit if there aren't any headers in the columns to render.
- boolean hasHeader = false;
- for (int i = 0; i < columnCount; i++) {
- if (getHeader(i) != null) {
- hasHeader = true;
- break;
- }
- }
- if (hasHeader == false) {
- return;
- }
-
- // Get information about the sorted column.
- ColumnSortList sortList = table.getColumnSortList();
- ColumnSortInfo sortedInfo = (sortList.size() == 0) ? null : sortList.get(0);
- Column<?, ?> sortedColumn = (sortedInfo == null) ? null : sortedInfo.getColumn();
- boolean isSortAscending = (sortedInfo == null) ? false : sortedInfo.isAscending();
-
- // Get the common style names.
- Style style = table.getResources().style();
- String className = isFooter ? style.footer() : style.header();
- String sortableStyle = " " + style.sortableHeader();
- String sortedStyle =
- " " + (isSortAscending ? style.sortedHeaderAscending() : style.sortedHeaderDescending());
-
- // Setup the first column.
- Header<?> prevHeader = getHeader(0);
- Column<T, ?> column = table.getColumn(0);
- int prevColspan = 1;
- boolean isSortable = false;
- boolean isSorted = false;
- StringBuilder classesBuilder = new StringBuilder(className);
- classesBuilder.append(" " + (isFooter ? style.firstColumnFooter() : style.firstColumnHeader()));
- if (!isFooter && column.isSortable()) {
- isSortable = true;
- isSorted = (column == sortedColumn);
- }
-
- // Loop through all column headers.
- TableRowBuilder tr = utility.startRow();
- int curColumn;
- for (curColumn = 1; curColumn < columnCount; curColumn++) {
- Header<?> header = getHeader(curColumn);
-
- if (header != prevHeader) {
- // The header has changed, so append the previous one.
- if (isSortable) {
- classesBuilder.append(sortableStyle);
- }
- if (isSorted) {
- classesBuilder.append(sortedStyle);
- }
-
- // Render the header.
- TableCellBuilder th =
- tr.startTH().colSpan(prevColspan).className(classesBuilder.toString());
- utility.enableColumnHandlers(th, column);
- if (prevHeader != null) {
- // Build the header.
- Context context = new Context(0, curColumn - prevColspan, prevHeader.getKey());
- renderHeader(th, context, prevHeader, utility, isSorted, isSortAscending);
- }
- th.endTH();
-
- // Reset the previous header.
- prevHeader = header;
- prevColspan = 1;
- classesBuilder = new StringBuilder(className);
- isSortable = false;
- isSorted = false;
- } else {
- // Increment the colspan if the headers == each other.
- prevColspan++;
- }
-
- // Update the sorted state.
- column = table.getColumn(curColumn);
- if (!isFooter && column.isSortable()) {
- isSortable = true;
- isSorted = (column == sortedColumn);
- }
- }
-
- // Append the last header.
- if (isSortable) {
- classesBuilder.append(sortableStyle);
- }
- if (isSorted) {
- classesBuilder.append(sortedStyle);
- }
-
- // The first and last columns could be the same column.
- classesBuilder.append(" ").append(
- isFooter ? style.lastColumnFooter() : style.lastColumnHeader());
-
- // Render the last header.
- TableCellBuilder th = tr.startTH().colSpan(prevColspan).className(classesBuilder.toString());
- utility.enableColumnHandlers(th, column);
- if (prevHeader != null) {
- Context context = new Context(0, curColumn - prevColspan, prevHeader.getKey());
- renderHeader(th, context, prevHeader, utility, isSorted, isSortAscending);
- }
- th.endTH();
-
- // End the row.
- tr.endTR();
- }
-
- /**
- * Check if the icon is located at the start or end of the line. The start of
- * the line refers to the left side in LTR mode and the right side in RTL
- * mode. The default location is the start of the line.
- */
- public boolean isSortIconStartOfLine() {
- return isSortIconStartOfLine;
- }
-
- /**
- * Set the position of the sort icon to the start or end of the line. The
- * start of the line refers to the left side in LTR mode and the right side in
- * RTL mode. The default location is the start of the line.
- */
- public void setSortIconStartOfLine(boolean isStartOfLine) {
- this.isSortIconStartOfLine = isStartOfLine;
- }
-
- /**
- * Get the header or footer at the specified index.
- *
- * @param index the column index of the header
- * @return the header or footer, depending on the value of isFooter
- */
- protected Header<?> getHeader(int index) {
- return isFooter ? getTable().getFooter(index) : getTable().getHeader(index);
- }
-
- protected AbstractCellTable<?> getTable() {
- return table;
- }
-
- /**
- * Render a header.
- *
- * @param out the builder to render into
- * @param header the header to render
- * @param context the context of the header
- * @param utility the utility used to render the header
- * @param isSorted true if the column is sorted
- * @param isSortAscending indicated the sort order, if sorted
- */
- protected void renderHeader(ElementBuilderBase<?> out, Context context, Header<?> header,
- Helper<T> utility, boolean isSorted, boolean isSortAscending) {
- ElementBuilderBase<?> headerContainer = out;
-
- // Wrap the header in a sort icon if sorted.
- isSorted = isSorted && !isFooter;
- if (isSorted) {
- // Determine the position of the sort icon.
- boolean posRight =
- LocaleInfo.getCurrentLocale().isRTL() ? isSortIconStartOfLine : !isSortIconStartOfLine;
-
- // Create an outer container to hold the icon and the header.
- int iconWidth = isSortAscending ? sortAscIconWidth : sortDescIconWidth;
- int halfHeight = isSortAscending ? sortAscIconHalfHeight : sortDescIconHalfHeight;
- DivBuilder outerDiv = out.startDiv();
- StylesBuilder style =
- outerDiv.style().position(Position.RELATIVE).trustedProperty("zoom", "1");
- if (posRight) {
- style.paddingRight(iconWidth, Unit.PX);
- } else {
- style.paddingLeft(iconWidth, Unit.PX);
- }
- style.endStyle();
-
- // Add the icon.
- DivBuilder imageHolder = outerDiv.startDiv();
- style =
- outerDiv.style().position(Position.ABSOLUTE).top(50.0, Unit.PCT).lineHeight(0.0, Unit.PX)
- .marginTop(-halfHeight, Unit.PX);
- if (posRight) {
- style.right(0, Unit.PX);
- } else {
- style.left(0, Unit.PX);
- }
-
- style.endStyle();
- imageHolder.html(getSortIcon(isSortAscending));
- imageHolder.endDiv();
-
- // Create the header wrapper.
- headerContainer = outerDiv.startDiv();
- }
-
- // Build the header.
- utility.renderHeader(headerContainer, context, header);
-
- // Close the elements used for the sort icon.
- if (isSorted) {
- headerContainer.endDiv(); // headerContainer.
- headerContainer.endDiv(); // outerDiv
- }
- }
-
- /**
- * Get the HTML representation of the sort icon. These are loaded lazily so
- * the compiler has a chance to strip this method, and the icon source code,
- * if the user overrides renderHeader.
- *
- * @param isAscending true for the ascending icon, false for descending
- * @return the rendered HTML
- */
- private SafeHtml getSortIcon(boolean isAscending) {
- if (isAscending) {
- if (sortAscIconHtml == null) {
- AbstractImagePrototype proto =
- AbstractImagePrototype.create(table.getResources().sortAscending());
- sortAscIconHtml = SafeHtmlUtils.fromTrustedString(proto.getHTML());
- }
- return sortAscIconHtml;
- } else {
- if (sortDescIconHtml == null) {
- AbstractImagePrototype proto =
- AbstractImagePrototype.create(table.getResources().sortDescending());
- sortDescIconHtml = SafeHtmlUtils.fromTrustedString(proto.getHTML());
- }
- return sortDescIconHtml;
- }
- }
-}
diff --git a/user/src/com/google/gwt/user/cellview/client/DefaultHeaderOrFooterBuilder.java b/user/src/com/google/gwt/user/cellview/client/DefaultHeaderOrFooterBuilder.java
new file mode 100644
index 0000000..564ba07
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/DefaultHeaderOrFooterBuilder.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.cellview.client;
+
+import com.google.gwt.cell.client.Cell.Context;
+import com.google.gwt.dom.builder.shared.TableCellBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
+import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
+
+/**
+ * Default implementation of {@link HeaderBuilder} that renders columns.
+ *
+ * @param <T> the data type of the table
+ */
+public class DefaultHeaderOrFooterBuilder<T> extends AbstractHeaderOrFooterBuilder<T> {
+
+ /**
+ * Create a new DefaultHeaderBuilder for the header of footer section.
+ *
+ * @param table the table being built
+ * @param isFooter true if building the footer, false if the header
+ */
+ public DefaultHeaderOrFooterBuilder(AbstractCellTable<T> table, boolean isFooter) {
+ super(table, isFooter);
+ }
+
+ @Override
+ protected boolean buildHeaderOrFooterImpl() {
+ AbstractCellTable<T> table = getTable();
+ boolean isFooter = isBuildingFooter();
+
+ // Early exit if there aren't any columns to render.
+ int columnCount = table.getColumnCount();
+ if (columnCount == 0) {
+ // Nothing to render;
+ return false;
+ }
+
+ // Early exit if there aren't any headers in the columns to render.
+ boolean hasHeader = false;
+ for (int i = 0; i < columnCount; i++) {
+ if (getHeader(i) != null) {
+ hasHeader = true;
+ break;
+ }
+ }
+ if (hasHeader == false) {
+ return false;
+ }
+
+ // Get information about the sorted column.
+ ColumnSortList sortList = table.getColumnSortList();
+ ColumnSortInfo sortedInfo = (sortList.size() == 0) ? null : sortList.get(0);
+ Column<?, ?> sortedColumn = (sortedInfo == null) ? null : sortedInfo.getColumn();
+ boolean isSortAscending = (sortedInfo == null) ? false : sortedInfo.isAscending();
+
+ // Get the common style names.
+ Style style = getTable().getResources().style();
+ String className = isBuildingFooter() ? style.footer() : style.header();
+ String sortableStyle = " " + style.sortableHeader();
+ String sortedStyle =
+ " " + (isSortAscending ? style.sortedHeaderAscending() : style.sortedHeaderDescending());
+
+ // Setup the first column.
+ Header<?> prevHeader = getHeader(0);
+ Column<T, ?> column = getTable().getColumn(0);
+ int prevColspan = 1;
+ boolean isSortable = false;
+ boolean isSorted = false;
+ StringBuilder classesBuilder = new StringBuilder(className);
+ classesBuilder.append(" " + (isFooter ? style.firstColumnFooter() : style.firstColumnHeader()));
+ if (!isFooter && column.isSortable()) {
+ isSortable = true;
+ isSorted = (column == sortedColumn);
+ }
+
+ // Loop through all column headers.
+ TableRowBuilder tr = startRow();
+ int curColumn;
+ for (curColumn = 1; curColumn < columnCount; curColumn++) {
+ Header<?> header = getHeader(curColumn);
+
+ if (header != prevHeader) {
+ // The header has changed, so append the previous one.
+ if (isSortable) {
+ classesBuilder.append(sortableStyle);
+ }
+ if (isSorted) {
+ classesBuilder.append(sortedStyle);
+ }
+
+ // Render the header.
+ TableCellBuilder th =
+ tr.startTH().colSpan(prevColspan).className(classesBuilder.toString());
+ enableColumnHandlers(th, column);
+ if (prevHeader != null) {
+ // Build the header.
+ Context context = new Context(0, curColumn - prevColspan, prevHeader.getKey());
+ renderSortableHeader(th, context, prevHeader, isSorted, isSortAscending);
+ }
+ th.endTH();
+
+ // Reset the previous header.
+ prevHeader = header;
+ prevColspan = 1;
+ classesBuilder = new StringBuilder(className);
+ isSortable = false;
+ isSorted = false;
+ } else {
+ // Increment the colspan if the headers == each other.
+ prevColspan++;
+ }
+
+ // Update the sorted state.
+ column = table.getColumn(curColumn);
+ if (!isFooter && column.isSortable()) {
+ isSortable = true;
+ isSorted = (column == sortedColumn);
+ }
+ }
+
+ // Append the last header.
+ if (isSortable) {
+ classesBuilder.append(sortableStyle);
+ }
+ if (isSorted) {
+ classesBuilder.append(sortedStyle);
+ }
+
+ // The first and last columns could be the same column.
+ classesBuilder.append(" ").append(
+ isFooter ? style.lastColumnFooter() : style.lastColumnHeader());
+
+ // Render the last header.
+ TableCellBuilder th = tr.startTH().colSpan(prevColspan).className(classesBuilder.toString());
+ enableColumnHandlers(th, column);
+ if (prevHeader != null) {
+ Context context = new Context(0, curColumn - prevColspan, prevHeader.getKey());
+ renderSortableHeader(th, context, prevHeader, isSorted, isSortAscending);
+ }
+ th.endTH();
+
+ // End the row.
+ tr.endTR();
+
+ return true;
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/FooterBuilder.java b/user/src/com/google/gwt/user/cellview/client/FooterBuilder.java
new file mode 100644
index 0000000..cef7c20
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/FooterBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.cellview.client;
+
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * Builds the DOM elements for the footer section of a CellTable. It also
+ * provides queries on elements in the last DOM subtree that it created.
+ *
+ * <p>
+ * {@link FooterBuilder} provides two optional ways to handle events, via a
+ * {@link Column}, a {@link Header}, or both. If {@link #getColumn(Element)}
+ * returns a {@link Column} given the target {@link Element} of an event, cell
+ * table will use it to enable features such as sorting. If
+ * {@link #getHeader(Element)} returns a {@link Header}, cell table will forward
+ * the event to the {@link Header}. You can specify both a {@link Column} and
+ * {@link Header}.
+ * </p>
+ *
+ * <p>
+ * The default implementation used by cell widgets is
+ * {@link DefaultHeaderOrFooterBuilder}.
+ * </p>
+ *
+ * @param <T> the row data type
+ */
+public interface FooterBuilder<T> {
+
+ /**
+ * Builds the DOM subtree for this footer. The root of the subtree must be a
+ * TFOOT element, as appropriate. This method may be called multiple times and
+ * should return a new DOM subtree each time.
+ *
+ * <p>
+ * If the footer is empty, return null.
+ * </p>
+ *
+ * @return a {@link TableSectionBuilder} representing the new footer, or null
+ * if the footer is empty
+ */
+ TableSectionBuilder buildFooter();
+
+ /**
+ * Given an element in the DOM subtree returned by the most recent call to
+ * {@link #buildFooter()}, returns the Column that should be the target of any
+ * button clicks or other events on that element, or null if the events should
+ * be discarded. The column is used to support features such as column
+ * sorting.
+ *
+ * @param elem the element that the contains column
+ * @return the immediate column contained by the element
+ */
+ Column<T, ?> getColumn(Element elem);
+
+ /**
+ * If you want to handle browser events using a subclass of {@link Header},
+ * implement this method to return the appropriate instance and cell table
+ * will forward events originating in the element to the {@link Header}.
+ * Return null if events from the element should be discarded.
+ *
+ * @param elem the element that the contains header
+ * @return the immediate {@link Header} contained by the element
+ */
+ Header<?> getHeader(Element elem);
+
+ /**
+ * Check if an element contains a {@link Column}. This method should return
+ * false if and only if {@link #getColumn(Element)} would return null.
+ *
+ * @param elem the element of interest
+ */
+ boolean isColumn(Element elem);
+
+ /**
+ * Check if an element contains a {@link Header}. This method should return
+ * false if and only if {@link #getHeader(Element)} would return null.
+ *
+ * @param elem the element of interest
+ */
+ boolean isHeader(Element elem);
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/HeaderBuilder.java b/user/src/com/google/gwt/user/cellview/client/HeaderBuilder.java
new file mode 100644
index 0000000..f13c011
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/HeaderBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.cellview.client;
+
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * Builds the DOM elements for the header section of a CellTable. It also
+ * provides queries on elements in the last DOM subtree that it created.
+ *
+ * <p>
+ * {@link HeaderBuilder} provides two optional ways to handle events, via a
+ * {@link Column}, a {@link Header}, or both. If {@link #getColumn(Element)}
+ * returns a {@link Column} given the target {@link Element} of an event, cell
+ * table will use it to enable features such as sorting. If
+ * {@link #getHeader(Element)} returns a {@link Header}, cell table will forward
+ * the event to the {@link Header}. You can specify both a {@link Column} and
+ * {@link Header}.
+ * </p>
+ *
+ * <p>
+ * The default implementation used by cell widgets is
+ * {@link DefaultHeaderOrFooterBuilder}.
+ * </p>
+ *
+ * @param <T> the row data type
+ */
+public interface HeaderBuilder<T> {
+
+ /**
+ * Builds the DOM subtree for this header. The root of the subtree must be a
+ * THEAD element, as appropriate. This method may be called multiple times and
+ * should return a new DOM subtree each time.
+ *
+ * <p>
+ * If the header is empty, return null.
+ * </p>
+ *
+ * @return a {@link TableSectionBuilder} representing the new header, or null
+ * if the header is empty
+ */
+ TableSectionBuilder buildHeader();
+
+ /**
+ * Given an element in the DOM subtree returned by the most recent call to
+ * {@link #buildHeader()}, returns the Column that should be the target of any
+ * button clicks or other events on that element, or null if the events should
+ * be discarded. The column is used to support features such as column
+ * sorting.
+ *
+ * @param elem the element that the contains column
+ * @return the immediate column contained by the element
+ */
+ Column<T, ?> getColumn(Element elem);
+
+ /**
+ * If you want to handle browser events using a subclass of {@link Header},
+ * implement this method to return the appropriate instance and cell table
+ * will forward events originating in the element to the {@link Header}.
+ * Return null if events from the element should be discarded.
+ *
+ * @param elem the element that the contains header
+ * @return the immediate {@link Header} contained by the element
+ */
+ Header<?> getHeader(Element elem);
+
+ /**
+ * Check if an element contains a {@link Column}. This method should return
+ * false if and only if {@link #getColumn(Element)} would return null.
+ *
+ * @param elem the element of interest
+ */
+ boolean isColumn(Element elem);
+
+ /**
+ * Check if an element contains a {@link Header}. This method should return
+ * false if and only if {@link #getHeader(Element)} would return null.
+ *
+ * @param elem the element of interest
+ */
+ boolean isHeader(Element elem);
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/HeaderCreator.java b/user/src/com/google/gwt/user/cellview/client/HeaderCreator.java
deleted file mode 100644
index 6b520eb..0000000
--- a/user/src/com/google/gwt/user/cellview/client/HeaderCreator.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.user.cellview.client;
-
-import com.google.gwt.cell.client.Cell.Context;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.TableRowBuilder;
-
-/**
- * Creates the DOM elements for the header or footer section of a CellTable.
- *
- * <p>
- * WARNING: This API is experimental and may change without warning.
- * </p>
- *
- * <p>
- * The default implementation used by cell widgets is
- * {@link DefaultHeaderCreator}.
- * </p>
- *
- * @param <T> the row data type
- */
-// TODO(jlabanca): Refactor to mirror CellTableBuilder API.
-// TODO(jlabanca): Add API to handle events in the header.
-public interface HeaderCreator<T> {
-
- /**
- * Contains methods that {@link #buildHeader} can call while building a header
- * or footer.
- *
- * <p>
- * The cell table being rendered will define the Helper implementation and use
- * it to ensure that events from the rendered table are handled correctly.
- * </p>
- *
- * @param <T> the row data type
- */
- // TODO(jlabanca): Refactor Helper out of existence.
- public abstract class Helper<T> {
-
- private final AbstractCellTable<T> cellTable;
-
- /**
- * Only instantiable by CellTable implementation.
- */
- /*
- * The helper class is defined by the CellTable implementation to register
- * headers and columns with the table. The constructor is package protected
- * so we can add methods in the future without breaking the API. Only
- * serious power users who are creating their own AbstractCellTable
- * implementation that uses some other method to register headers should
- * ever need to implement Helper.
- */
- Helper(AbstractCellTable<T> cellTable) {
- this.cellTable = cellTable;
- }
-
- /**
- * Enables column-specific event handling for the specified element. If a
- * column is sortable, then clicking on the element or a child of the
- * element will trigger a sort event.
- *
- * @param builder the builder to associate with the column. The builder
- * should be a child element of a row returned by {@link #startRow}
- * and must be in a state where an attribute can be added.
- * @param column the column to associate
- */
- public abstract void enableColumnHandlers(ElementBuilderBase<?> builder, Column<T, ?> column);
-
- /**
- * Get the cell table that is being built.
- */
- public AbstractCellTable<T> getTable() {
- return cellTable;
- }
-
- /**
- * Renders a given Header into a given ElementBuilderBase. This method
- * ensures that the CellTable widget will handle events events originating
- * in the Header.
- *
- * @param <H> the data type of the header
- * @param out the {@link ElementBuilderBase} to render into. The builder
- * should be a child element of a row returned by {@link #startRow}
- * and must be in a state that allows both attributes and elements
- * to be added
- * @param context the {@link Context} of the header being rendered
- * @param header the {@link Header} to render
- */
- public abstract <H> void renderHeader(ElementBuilderBase<?> out, Context context,
- Header<H> header);
-
- /**
- * Add a header (or footer) row to the table, below any rows previously
- * added.
- *
- * @return the row to add
- */
- public abstract TableRowBuilder startRow();
- }
-
- /**
- * Build the entire header table.
- *
- * <p>
- * The {@link Helper} should be used to add rows to the table, associate
- * elements with a {@link Column}, and render {@link Header}s into elements.
- * </p>
- *
- * @param helper a {@link Helper} for adding headers
- */
- void buildHeader(Helper<T> helper);
-}
diff --git a/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java b/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
index d3f9b5d..7dfffd0 100644
--- a/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
+++ b/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
@@ -732,22 +732,23 @@
RootPanel.get().remove(table);
}
- public void testSetHeaderCreator() {
+ public void testSetHeaderBuilder() {
T table = createAbstractHasData();
- HeaderCreator<String> headerCreator = new HeaderCreator<String>() {
+ HeaderBuilder<String> headerBuilder = new AbstractHeaderOrFooterBuilder<String>(table, false) {
@Override
- public void buildHeader(HeaderCreator.Helper<String> utility) {
- TableRowBuilder tr = utility.startRow();
+ protected boolean buildHeaderOrFooterImpl() {
+ TableRowBuilder tr = startRow();
tr.startTH().text("Col 0").endTH();
tr.startTH().text("Col 1").endTH();
tr.startTH().text("Col 2").endTH();
tr.endTR();
+ return true;
}
};
// Change the header builder.
- table.setHeaderCreator(headerCreator);
- assertEquals(headerCreator, table.getHeaderCreator());
+ table.setHeaderBuilder(headerBuilder);
+ assertEquals(headerBuilder, table.getHeaderBuilder());
table.getPresenter().flush();
// Verify the new header.
@@ -760,22 +761,23 @@
assertEquals("Col 2", cells.getItem(2).getInnerText());
}
- public void testSetFooterCreator() {
+ public void testSetFooterBuilder() {
T table = createAbstractHasData();
- HeaderCreator<String> footerCreator = new HeaderCreator<String>() {
+ FooterBuilder<String> footerBuilder = new AbstractHeaderOrFooterBuilder<String>(table, true) {
@Override
- public void buildHeader(HeaderCreator.Helper<String> utility) {
- TableRowBuilder tr = utility.startRow();
+ protected boolean buildHeaderOrFooterImpl() {
+ TableRowBuilder tr = startRow();
tr.startTH().text("Col 0").endTH();
tr.startTH().text("Col 1").endTH();
tr.startTH().text("Col 2").endTH();
tr.endTR();
+ return true;
}
};
// Change the header builder.
- table.setFooterCreator(footerCreator);
- assertEquals(footerCreator, table.getFooterCreator());
+ table.setFooterBuilder(footerBuilder);
+ assertEquals(footerBuilder, table.getFooterBuilder());
table.getPresenter().flush();
// Verify the new header.
diff --git a/user/test/com/google/gwt/user/cellview/client/DataGridTest.java b/user/test/com/google/gwt/user/cellview/client/DataGridTest.java
index a53adb7..d3fcac3 100644
--- a/user/test/com/google/gwt/user/cellview/client/DataGridTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/DataGridTest.java
@@ -27,34 +27,46 @@
public class DataGridTest extends AbstractCellTableTestBase<DataGrid<String>> {
/**
- * Test that if a header creator does not add any rows, the header is hidden.
+ * Test that if a header builder does not add any rows, the header is hidden.
*/
- public void testHeaderCreatorEmpty() {
+ public void testHeaderBuilderEmpty() {
DataGrid<String> table = createAbstractHasData();
RootPanel.get().add(table);
- HeaderCreator<String> emptyBuilder = new HeaderCreator<String>() {
+ HeaderBuilder<String> emptyHeader = new AbstractHeaderOrFooterBuilder<String>(table, false) {
@Override
- public void buildHeader(HeaderCreator.Helper<String> utility) {
- // No-op.
+ protected boolean buildHeaderOrFooterImpl() {
+ return false;
}
};
- HeaderCreator<String> notEmptyBuilder = new HeaderCreator<String>() {
+ HeaderBuilder<String> notEmptyHeader = new AbstractHeaderOrFooterBuilder<String>(table, false) {
@Override
- public void buildHeader(HeaderCreator.Helper<String> utility) {
- utility.startRow();
+ protected boolean buildHeaderOrFooterImpl() {
+ return true;
+ }
+ };
+ FooterBuilder<String> emptyFooter = new AbstractHeaderOrFooterBuilder<String>(table, true) {
+ @Override
+ protected boolean buildHeaderOrFooterImpl() {
+ return false;
+ }
+ };
+ FooterBuilder<String> notEmptyFooter = new AbstractHeaderOrFooterBuilder<String>(table, true) {
+ @Override
+ protected boolean buildHeaderOrFooterImpl() {
+ return true;
}
};
// Header is empty, footer is not.
- table.setHeaderCreator(emptyBuilder);
- table.setFooterCreator(notEmptyBuilder);
+ table.setHeaderBuilder(emptyHeader);
+ table.setFooterBuilder(notEmptyFooter);
table.getPresenter().flush();
assertFalse(table.tableHeader.isAttached());
assertTrue(table.tableFooter.isAttached());
// Footer is empty, header is not.
- table.setHeaderCreator(notEmptyBuilder);
- table.setFooterCreator(emptyBuilder);
+ table.setHeaderBuilder(notEmptyHeader);
+ table.setFooterBuilder(emptyFooter);
table.getPresenter().flush();
assertTrue(table.tableHeader.isAttached());
assertFalse(table.tableFooter.isAttached());