Revision: 10550
Author: [email protected]
Date: Thu Aug 18 07:20:07 2011
Log: Revise CellTableBuilder API. In the earlier version, a Utility
class is used to
render the rows/cells. The utility performs various checks to ensure the
proper
DOM structure. It also appends a cell id to each TD element so that it can
retrieve the Column quickly during event handling. These introduce
additional
rendering latency and could become problematic for tables of extremely large
size and/or in slow browsers. This change removes the Utility class and
replace
it with AbstractCellTableBuilder. This makes the CellTableBuilder API more
flexible. Users can choose to implement a CellTableBuilder without the above
mentioned overheads, or subclass AbstractCellTableBuilder, in a way almost
identical to the earlier table builder version.
A method getColumn(Context context, T rowValue, Element elem) is added to
the
CellTableBuilder interface. User can implement this method to tell the
CellTable
which column contains a particular element (e.g., the event target
element). If
all columns are renderer, users may simply return
CellTable.getColumn(context.col). Or, users can "replay" the row building
logic
to locate the column for the element. Of course the cell id based approach
is
still supported and is now in AbstractCellTableBuilder. Two similar methods
isColumn and getColumns are also added. Please see javadoc for more details.
The buildRows method is refactored to remove the utility. Users can choose
to
still use an element builder based approach (see AbstractCellTableBuilder),
in
which each time a cell is rendered, checks are performed to ensure the
proper
DOM structure. Or, users can choose to use a simple string builder based
approach and create a TableSectionBuilder in the new "finish()" method
(e.g.,
using the new FastHtmlTableSectionBuilder in this change). This
will be much faster, won't be able to detect a malformed html.
Review at http://gwt-code-reviews.appspot.com/1527803
http://code.google.com/p/google-web-toolkit/source/detail?r=10550
Added:
/trunk/user/src/com/google/gwt/dom/builder/shared/HtmlOnlyTableSectionBuilder.java
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTableBuilder.java
Modified:
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
/trunk/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/dom/builder/shared/HtmlOnlyTableSectionBuilder.java
Thu Aug 18 07:20:07 2011
@@ -0,0 +1,81 @@
+/*
+ * 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.dom.builder.shared;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * A subclass of standard {@link HtmlTableSectionBuilder} that allows
directly appending html to
+ * a table section.
+ * <p>
+ * Some browsers do not support setting innerHTML on a table section
element (such
+ * as a tbody), so both the DOM and HTML based implementations throw an
error to
+ * ensure consistency. This class exists to allow users to append HTML to a
+ * TableSectionBuilder if they opt into the HTML version.
+ * </p>
+ */
+public final class HtmlOnlyTableSectionBuilder extends
HtmlTableSectionBuilder {
+
+ /**
+ * Create and return a table body section builder.
+ */
+ public static HtmlOnlyTableSectionBuilder createTBodyBuilder() {
+ HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+ htmlBuilderImpl.startTBody();
+ return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+ }
+
+ /**
+ * Create and return a table footer section builder.
+ */
+ public static HtmlOnlyTableSectionBuilder createTFootBuilder() {
+ HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+ htmlBuilderImpl.startTFoot();
+ return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+ }
+
+ /**
+ * Create and return a table header section builder.
+ */
+ public static HtmlOnlyTableSectionBuilder createTHeadBuilder() {
+ HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+ htmlBuilderImpl.startTHead();
+ return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+ }
+
+ private HtmlBuilderImpl delegate;
+
+ /**
+ * Construct a new html table section builder.
+ *
+ * @param delegate delegate the html based delegate that builds the
element
+ */
+ private HtmlOnlyTableSectionBuilder(HtmlBuilderImpl delegate) {
+ super(delegate);
+ this.delegate = delegate;
+ }
+
+ /**
+ * Append html to the builder and validate the correctness of the html.
+ *
+ * @param html the html for the table section
+ */
+ @Override
+ public TableSectionBuilder html(SafeHtml html) {
+ delegate.html(html);
+ return getReturnBuilder();
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTableBuilder.java
Thu Aug 18 07:20:07 2011
@@ -0,0 +1,305 @@
+/*
+ * 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.cell.client.HasCell;
+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;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builder used to construct a CellTable.
+ *
+ * @param <T> the row data type
+ */
+public abstract class AbstractCellTableBuilder<T> implements
CellTableBuilder<T> {
+
+ /**
+ * The attribute used to indicate that an element contains a cell.
+ */
+ private static final String CELL_ATTRIBUTE = "__gwt_cell";
+
+ /**
+ * The attribute used to specify the logical row index.
+ */
+ private static final String ROW_ATTRIBUTE = "__gwt_row";
+
+ /**
+ * The attribute used to specify the subrow within a logical row value.
+ */
+ private static final String SUBROW_ATTRIBUTE = "__gwt_subrow";
+
+ protected final AbstractCellTable<T> cellTable;
+
+ /**
+ * A mapping of unique cell IDs to the cell.
+ */
+ private final Map<String, HasCell<T, ?>> idToCellMap = new
HashMap<String, HasCell<T, ?>>();
+ private final Map<HasCell<T, ?>, String> cellToIdMap = new
HashMap<HasCell<T, ?>, String>();
+
+ private HtmlTableSectionBuilder tbody;
+ private int rowIndex;
+ private int subrowIndex;
+ private Object rowValueKey;
+
+ /**
+ * Construct a new table builder.
+ *
+ * @param cellTable the table this builder will build rows for
+ */
+ public AbstractCellTableBuilder(AbstractCellTable<T> cellTable) {
+ this.cellTable = cellTable;
+ }
+
+ /**
+ * Build zero or more table rows for the specified row value.
+ *
+ * @param rowValue the value for the row to render
+ * @param absRowIndex the absolute row index
+ */
+ @Override
+ public final void buildRow(T rowValue, int absRowIndex) {
+ setRowInfo(absRowIndex, rowValue);
+ buildRowImpl(rowValue, absRowIndex);
+ }
+
+ /**
+ * Create the context for a column based on the current table building
state.
+ *
+ * @param column the column id
+ * @return the context that contains the column index, row/subrow
indexes, and the row value key
+ */
+ public final Context createContext(int column) {
+ return new Context(rowIndex, column, rowValueKey, subrowIndex);
+ }
+
+ /**
+ * Finish the building and get the {@link TableSectionBuilder}
containing the children.
+ */
+ @Override
+ public final TableSectionBuilder finish() {
+ // End dangling elements.
+ while (tbody.getDepth() > 0) {
+ tbody.endTBody();
+ }
+ return tbody;
+ }
+
+ /**
+ * Return the column containing an element.
+ *
+ * @param context the context for the element
+ * @param rowValue the value for the row corresponding to the element
+ * @param elem the element that the column contains
+ * @return the immediate column containing the element
+ */
+ @Override
+ public final HasCell<T, ?> getColumn(Context context, T rowValue,
Element elem) {
+ return getColumn(elem);
+ }
+
+ /**
+ * Return all the columns that this table builder has renderred.
+ */
+ @Override
+ public final Collection<HasCell<T, ?>> getColumns() {
+ return idToCellMap.values();
+ }
+
+ /**
+ * Get the index of the row value from the associated {@link
TableRowElement}.
+ *
+ * @param row the row element
+ * @return the row value index
+ */
+ @Override
+ public final int getRowValueIndex(TableRowElement row) {
+ try {
+ return Integer.parseInt(row.getAttribute(ROW_ATTRIBUTE));
+ } catch (NumberFormatException e) {
+ // The attribute doesn't exist. Maybe the user is overriding
+ // renderRowValues().
+ return row.getSectionRowIndex() + cellTable.getPageStart();
+ }
+ }
+
+ /**
+ * Get the index of the subrow value from the associated
+ * {@link TableRowElement}. The sub row value starts at 0 for the first
row
+ * that represents a row value.
+ *
+ * @param row the row element
+ * @return the subrow value index, or 0 if not found
+ */
+ @Override
+ public final int getSubrowValueIndex(TableRowElement row) {
+ try {
+ return Integer.parseInt(row.getAttribute(SUBROW_ATTRIBUTE));
+ } catch (NumberFormatException e) {
+ // The attribute doesn't exist. Maybe the user is overriding
+ // renderRowValues() in {@link AbstractCellTable}.
+ return 0;
+ }
+ }
+
+ /**
+ * Return if an element contains a cell. This may be faster to execute
than {@link getColumn}.
+ *
+ * @param elem the element of interest
+ */
+ @Override
+ public final boolean isColumn(Element elem) {
+ return getCellId(elem) != null;
+ }
+
+ /**
+ * Render the cell into an {@link ElementBuilderBase}.
+ *
+ * @param builder the {@link ElementBuilderBase} that cell contents
append to
+ * @param context the context for the element
+ * @param column the column containing the cell
+ * @param rowValue the value for the row corresponding to the element
+ */
+ public final <C> void renderCell(ElementBuilderBase<?> builder, Context
context,
+ HasCell<T, C> column, T rowValue) {
+ // Generate a unique ID for the cell.
+ String cellId = cellToIdMap.get(column);
+ if (cellId == null) {
+ cellId = "cell-" + Document.get().createUniqueId();
+ idToCellMap.put(cellId, column);
+ cellToIdMap.put(column, cellId);
+ }
+ builder.attribute(CELL_ATTRIBUTE, cellId);
+
+ // Render the cell into the builder.
+ SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+ if (column instanceof Column) {
+ /*
+ * If the HasCell is a Column, let it render the Cell itself. This is
+ * here for legacy support.
+ */
+ Column<T, C> theColumn = (Column<T, C>) column;
+ theColumn.render(context, rowValue, cellBuilder);
+ } else {
+ column.getCell().render(context, column.getValue(rowValue),
cellBuilder);
+ }
+ builder.html(cellBuilder.toSafeHtml());
+ }
+
+ /**
+ *
+ */
+ /**
+ * Start building rows. Reset the internal table section builder. If the
table builder is going
+ * to re-build all rows, the internal the maps associating the cells and
ids will be cleared.
+ *
+ * @param isRebuildingAllRows is this start intended for rebuilding all
rows
+ */
+ @Override
+ public final void start(boolean isRebuildingAllRows) {
+ /*
+ * TODO(jlabanca): Test with DomBuilder.
+ *
+ * DOM manipulation is sometimes faster than String concatenation and
+ * innerHTML, but not when mixing the two. Cells render as HTML
strings,
+ * so its faster to render the entire table as a string.
+ */
+ tbody = HtmlBuilderFactory.get().createTBodyBuilder();
+ if (isRebuildingAllRows) {
+ cellToIdMap.clear();
+ idToCellMap.clear();
+ }
+ }
+
+ /**
+ * Start a row and return the {@link TableRowBuilder} for this row.
+ */
+ public final TableRowBuilder startRow() {
+ // End any dangling rows.
+ while (tbody.getDepth() > 1) {
+ tbody.end();
+ }
+
+ // Verify the depth.
+ if (tbody.getDepth() < 1) {
+ throw new IllegalStateException(
+ "Cannot start a row. Did you call TableRowBuilder.end() too
many times?");
+ }
+
+ // Start the next row.
+ TableRowBuilder row = tbody.startTR();
+ row.attribute(ROW_ATTRIBUTE, rowIndex);
+ row.attribute(SUBROW_ATTRIBUTE, subrowIndex);
+ subrowIndex++;
+ return row;
+ }
+
+ /**
+ * Build zero or more table rows for the specified row value.
+ *
+ * @param rowValue the value for the row to render
+ * @param absRowIndex the absolute row index
+ */
+ protected abstract void buildRowImpl(T rowValue, int absRowIndex);
+
+ /**
+ * Check if an element is the parent of a rendered cell.
+ *
+ * @param elem the element to check
+ * @return the cellId if a cell parent, null if not
+ */
+ private String getCellId(Element elem) {
+ if (elem == null) {
+ return null;
+ }
+ String cellId = elem.getAttribute(CELL_ATTRIBUTE);
+ return (cellId == null) || (cellId.length() == 0) ? null : cellId;
+ }
+
+ /**
+ * Return the column containing an element.
+ *
+ * @param elem the elm that the column contains
+ * @return the column containing the element.
+ */
+ private HasCell<T, ?> getColumn(Element elem) {
+ String cellId = getCellId(elem);
+ return (cellId == null) ? null : idToCellMap.get(cellId);
+ }
+
+ /**
+ * Set the information for the current row to build.
+ *
+ * @param rowIndex the index of the row
+ * @param rowValue the value of this row
+ */
+ private void setRowInfo(int rowIndex, T rowValue) {
+ this.rowIndex = rowIndex;
+ this.rowValueKey = cellTable.getValueKey(rowValue);
+ this.subrowIndex = 0; // Reset the subrow.
+ }
+}
=======================================
---
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
Fri Aug 12 07:07:12 2011
+++
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
Thu Aug 18 07:20:07 2011
@@ -47,7 +47,7 @@
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
-import com.google.gwt.user.cellview.client.CellTableBuilder;
+import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.DataGrid;
@@ -132,7 +132,7 @@
* A custom version of {@link CellTableBuilder}.
*/
@ShowcaseSource
- private class CustomTableBuilder implements
CellTableBuilder<ContactInfo> {
+ private class CustomTableBuilder extends
AbstractCellTableBuilder<ContactInfo> {
private final int todayMonth;
private final Set<Integer> showingFriends = new HashSet<Integer>();
@@ -145,6 +145,7 @@
@SuppressWarnings("deprecation")
public CustomTableBuilder(ListHandler<ContactInfo> sortHandler) {
+ super(dataGrid);
// Cache styles for faster access.
Style style = dataGrid.getResources().style();
rowStyle = style.evenRow();
@@ -343,14 +344,13 @@
@SuppressWarnings("deprecation")
@Override
- public void buildRow(ContactInfo rowValue, int absRowIndex,
- CellTableBuilder.Utility<ContactInfo> utility) {
- buildContactRow(rowValue, absRowIndex, utility, false);
+ public void buildRowImpl(ContactInfo rowValue, int absRowIndex) {
+ buildContactRow(rowValue, absRowIndex, false);
// Display information about the user in another row that spans the
entire
// table.
if (rowValue.getAge() > 65) {
- TableRowBuilder row = utility.startRow();
+ 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();
@@ -361,7 +361,7 @@
// table.
Date dob = rowValue.getBirthday();
if (dob.getMonth() == todayMonth) {
- TableRowBuilder row = utility.startRow();
+ TableRowBuilder row = startRow();
TableCellBuilder td =
row.startTD().colSpan(7).className(cellStyle);
td.style().trustedBackgroundColor("#ccf").endStyle();
td.text(rowValue.getFirstName() + "'s birthday is this
month!").endTD();
@@ -372,7 +372,7 @@
if (showingFriends.contains(rowValue.getId())) {
Set<ContactInfo> friends =
ContactDatabase.get().queryFriends(rowValue);
for (ContactInfo friend : friends) {
- buildContactRow(friend, absRowIndex, utility, true);
+ buildContactRow(friend, absRowIndex, true);
}
}
}
@@ -382,12 +382,10 @@
*
* @param rowValue the contact info
* @param absRowIndex the absolute row index
- * @param utility the utility used to add rows and Cells
* @param isFriend true if this is a subrow, false if a top level row
*/
@SuppressWarnings("deprecation")
- private void buildContactRow(ContactInfo rowValue, int absRowIndex,
- CellTableBuilder.Utility<ContactInfo> utility, boolean isFriend) {
+ private void buildContactRow(ContactInfo rowValue, int absRowIndex,
boolean isFriend) {
// Calculate the row styles.
SelectionModel<? super ContactInfo> selectionModel =
dataGrid.getSelectionModel();
boolean isSelected =
@@ -408,7 +406,7 @@
cellStyles += childCell;
}
- TableRowBuilder row = utility.startRow();
+ TableRowBuilder row = startRow();
row.className(trClasses.toString());
/*
@@ -422,7 +420,7 @@
td.className(cellStyles);
td.style().outlineStyle(OutlineStyle.NONE).endStyle();
if (!isFriend) {
- utility.renderCell(td, utility.createContext(0),
dataGrid.getColumn(0), rowValue);
+ renderCell(td, createContext(0), dataGrid.getColumn(0), rowValue);
}
td.endTD();
@@ -436,7 +434,7 @@
td.className(cellStyles);
if (!isFriend) {
td.style().outlineStyle(OutlineStyle.NONE).endStyle();
- utility.renderCell(td, utility.createContext(1),
dataGrid.getColumn(1), rowValue);
+ renderCell(td, createContext(1), dataGrid.getColumn(1), rowValue);
}
td.endTD();
@@ -447,7 +445,7 @@
if (isFriend) {
td.text(rowValue.getFirstName());
} else {
- utility.renderCell(td, utility.createContext(2),
dataGrid.getColumn(2), rowValue);
+ renderCell(td, createContext(2), dataGrid.getColumn(2), rowValue);
}
td.endTD();
@@ -458,7 +456,7 @@
if (isFriend) {
td.text(rowValue.getLastName());
} else {
- utility.renderCell(td, utility.createContext(3),
dataGrid.getColumn(3), rowValue);
+ renderCell(td, createContext(3), dataGrid.getColumn(3), rowValue);
}
td.endTD();
@@ -475,7 +473,7 @@
if (isFriend) {
td.text(rowValue.getCategory().getDisplayName());
} else {
- utility.renderCell(td, utility.createContext(5),
dataGrid.getColumn(5), rowValue);
+ renderCell(td, createContext(5), dataGrid.getColumn(5), rowValue);
}
td.endTD();
=======================================
---
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
Fri Aug 12 07:07:12 2011
+++
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
Thu Aug 18 07:20:07 2011
@@ -25,11 +25,10 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
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.TableCellBuilder;
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.EventTarget;
@@ -250,9 +249,7 @@
*
* @param <T> the data type of the rows.
*/
- public static class DefaultCellTableBuilder<T> implements
CellTableBuilder<T> {
-
- private AbstractCellTable<T> cellTable;
+ public static class DefaultCellTableBuilder<T> extends
AbstractCellTableBuilder<T> {
private final String evenRowStyle;
private final String oddRowStyle;
@@ -265,8 +262,7 @@
private final String selectedCellStyle;
public DefaultCellTableBuilder(AbstractCellTable<T> cellTable) {
- this.cellTable = cellTable;
-
+ super(cellTable);
// Cache styles for faster access.
Style style = cellTable.getResources().style();
evenRowStyle = style.evenRow();
@@ -279,9 +275,9 @@
lastColumnStyle = " " + style.lastColumn();
selectedCellStyle = " " + style.selectedRowCell();
}
-
+
@Override
- public void buildRow(T rowValue, int absRowIndex,
CellTableBuilder.Utility<T> utility) {
+ public void buildRowImpl(T rowValue, int absRowIndex) {
// Calculate the row styles.
SelectionModel<? super T> selectionModel =
cellTable.getSelectionModel();
@@ -304,7 +300,7 @@
}
// Build the row.
- TableRowBuilder tr = utility.startRow();
+ TableRowBuilder tr = startRow();
tr.className(trClasses.toString());
// Build the columns.
@@ -349,7 +345,7 @@
div.style().outlineStyle(OutlineStyle.NONE).endStyle();
// Render the cell into the div.
- utility.renderCell(div, context, column, rowValue);
+ renderCell(div, context, column, rowValue);
// End the cell.
div.endDiv();
@@ -671,8 +667,8 @@
count++;
}
} else {
- while (insertBefore != null
- && table.getRowValueIndex(insertBefore.<TableRowElement>
cast()) < absEndIndex) {
+ while (insertBefore != null && table.tableBuilder.getRowValueIndex(
+ insertBefore.<TableRowElement> cast()) < absEndIndex) {
Element next = insertBefore.getNextSiblingElement();
section.removeChild(insertBefore);
insertBefore = next;
@@ -816,119 +812,7 @@
}
/**
- * Implementation for {@link CellTableBuilder.Utility}.
- */
- private class UtilityImpl extends CellTableBuilder.Utility<T> {
-
- private int rowIndex;
- private int subrowIndex;
- private Object rowValueKey;
- private final HtmlTableSectionBuilder tbody;
-
- private UtilityImpl() {
- /*
- * TODO(jlabanca): Test with DomBuilder.
- *
- * DOM manipulation is sometimes faster than String concatenation and
- * innerHTML, but not when mixing the two. Cells render as HTML
strings,
- * so its faster to render the entire table as a string.
- */
- tbody = HtmlBuilderFactory.get().createTBodyBuilder();
- }
-
- @Override
- public Context createContext(int column) {
- return new Context(rowIndex, column, rowValueKey);
- }
-
- @Override
- public <C> void renderCell(ElementBuilderBase<?> builder, Context
context,
- HasCell<T, C> column, T rowValue) {
- // Generate a unique ID for the cell.
- String cellId = cellToIdMap.get(column);
- if (cellId == null) {
- cellId = "cell-" + Document.get().createUniqueId();
- idToCellMap.put(cellId, column);
- cellToIdMap.put(column, cellId);
- }
- builder.attribute(CELL_ATTRIBUTE, cellId);
-
- // Render the cell into the builder.
- SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
- if (column instanceof Column) {
- /*
- * If the HasCell is a Column, let it render the Cell itself. This
is
- * here for legacy support.
- */
- Column<T, C> theColumn = (Column<T, C>) column;
- theColumn.render(context, rowValue, cellBuilder);
- } else {
- column.getCell().render(context, column.getValue(rowValue),
cellBuilder);
- }
- builder.html(cellBuilder.toSafeHtml());
- }
-
- @Override
- public TableRowBuilder startRow() {
- // End any dangling rows.
- while (tbody.getDepth() > 1) {
- tbody.end();
- }
-
- // Verify the depth.
- if (tbody.getDepth() < 1) {
- throw new IllegalStateException(
- "Cannot start a row. Did you call TableRowBuilder.end() too
many times?");
- }
-
- // Start the next row.
- TableRowBuilder row = tbody.startTR();
- row.attribute(ROW_ATTRIBUTE, rowIndex);
- row.attribute(SUBROW_ATTRIBUTE, subrowIndex);
- subrowIndex++;
- return row;
- }
-
- /**
- * Get the {@link TableSectionElement} containing the children.
- */
- private SafeHtml asSafeHtml() {
- // End dangling elements.
- while (tbody.getDepth() > 0) {
- tbody.endTBody();
- }
-
- // Strip the table section tags off of the tbody.
- String rawHtml = tbody.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);
- }
-
- private void setRowInfo(int rowIndex, T rowValue) {
- this.rowIndex = rowIndex;
- this.rowValueKey = getValueKey(rowValue);
- this.subrowIndex = 0; // Reset the subrow.
- }
- }
-
- /**
- * The attribute used to indicate that an element contains a cell.
- */
- private static final String CELL_ATTRIBUTE = "__gwt_cell";
-
- /**
- * The attribute used to specify the logical row index.
- */
- private static final String ROW_ATTRIBUTE = "__gwt_row";
-
- /**
- * The attribute used to specify the subrow within a logical row value.
- */
- private static final String SUBROW_ATTRIBUTE = "__gwt_subrow";
-
- /**
+
* The table specific {@link Impl}.
*/
private static Impl TABLE_IMPL;
@@ -942,12 +826,6 @@
Set<String> consumedEvents = column.getCell().getConsumedEvents();
return consumedEvents != null && consumedEvents.size() > 0;
}
-
- /**
- * A mapping of unique cell IDs to the cell.
- */
- private final Map<String, HasCell<T, ?>> idToCellMap = new
HashMap<String, HasCell<T, ?>>();
- private final Map<HasCell<T, ?>, String> cellToIdMap = new
HashMap<HasCell<T, ?>, String>();
private boolean cellIsEditing;
private final List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
@@ -1172,7 +1050,7 @@
public void flush() {
getPresenter().flush();
}
-
+
/**
* Get the column at the specified index.
*
@@ -1790,7 +1668,6 @@
TableSectionElement targetTableSection = null;
TableCellElement targetTableCell = null;
Element cellParent = null;
- String cellId = null;
{
Element maybeTableCell = null;
Element cur = target;
@@ -1819,9 +1696,8 @@
}
// Look for the most immediate cell parent if not already found.
- String curCellId = isCellParent(cur);
- if (cellParent == null && curCellId != null) {
- cellId = curCellId;
+ boolean isColumn = tableBuilder.isColumn(cur);
+ if (cellParent == null && isColumn) {
cellParent = cur;
}
@@ -1883,9 +1759,9 @@
* Get the row index of the data value. This may not correspond to
the DOM
* row index if the user specifies multiple table rows per row
object.
*/
- int absRow = getRowValueIndex(targetTableRow);
+ int absRow = tableBuilder.getRowValueIndex(targetTableRow);
int relRow = absRow - getPageStart();
- int subrow = getSubrowValueIndex(targetTableRow);
+ int subrow = tableBuilder.getSubrowValueIndex(targetTableRow);
if ("mouseover".equals(eventType)) {
// Unstyle the old row if it is still part of the table.
if (hoveringRow != null &&
getTableBodyElement().isOrHasChild(hoveringRow)) {
@@ -1920,11 +1796,15 @@
// Pass the event to the cell.
if (cellParent != null && !previewEvent.isCanceled()) {
- HasCell<T, ?> column = idToCellMap.get(cellId);
+ HasCell<T, ?> column;
if (legacyRenderRowValues) {
column = columns.get(col);
- }
- fireEventToCell(event, eventType, cellParent, value, context,
column);
+ } else {
+ column = tableBuilder.getColumn(context, value, cellParent);
+ }
+ if (column != null) {
+ fireEventToCell(event, eventType, cellParent, value, context,
column);
+ }
}
}
}
@@ -2082,9 +1962,7 @@
* but still supported.
*/
if (html == null) {
- cellToIdMap.clear();
- idToCellMap.clear();
- html = buildRowValues(values, getPageStart());
+ html = buildRowValues(values, getPageStart(), true);
}
TABLE_IMPL.replaceAllRows(this, getTableBodyElement(),
CellBasedWidgetImpl.get().processHtml(
@@ -2102,7 +1980,7 @@
* but still supported.
*/
if (html == null) {
- html = buildRowValues(values, getPageStart() + start);
+ html = buildRowValues(values, getPageStart() + start, false);
}
TABLE_IMPL.replaceChildren(this, getTableBodyElement(),
CellBasedWidgetImpl.get().processHtml(
@@ -2117,17 +1995,19 @@
return false;
}
- String cellId = isCellParent(elem);
- if (cellId == null) {
+ int row = getKeyboardSelectedRow();
+ int col = getKeyboardSelectedColumn();
+ T value = getVisibleItem(row);
+ Object key = getValueKey(value);
+ // TODO(pengzhuang): this doesn't support sub row selection?
+ Context context = new Context(row + getPageStart(), col, key);
+ HasCell<T, ?> column = tableBuilder.getColumn(context, value, elem);
+ if (column == null) {
// The selected element does not contain a Cell.
return false;
}
- HasCell<T, ?> column = idToCellMap.get(cellId);
- if (column != null) {
- resetFocusOnCellImpl(getKeyboardSelectedRow(),
getKeyboardSelectedColumn(), column, elem);
- }
-
+ resetFocusOnCellImpl(context, value, column, elem);
return false;
}
@@ -2212,10 +2092,10 @@
int domIndex = Math.min(relRow, frameEnd);
while (domIndex >= frameStart && domIndex <= frameEnd) {
TableRowElement curRow = rows.getItem(domIndex);
- int rowValueIndex = getRowValueIndex(curRow);
+ int rowValueIndex = tableBuilder.getRowValueIndex(curRow);
if (rowValueIndex == absRow) {
// Found a subrow in the row index.
- int subrowValueIndex = getSubrowValueIndex(curRow);
+ int subrowValueIndex = tableBuilder.getSubrowValueIndex(curRow);
if (subrow != subrowValueIndex) {
// Shift to the correct subrow.
int offset = subrow - subrowValueIndex;
@@ -2225,7 +2105,7 @@
return null;
}
curRow = rows.getItem(subrowIndex);
- if (getRowValueIndex(curRow) != absRow) {
+ if (tableBuilder.getRowValueIndex(curRow) != absRow) {
// The "subrow" is actually part of the next row.
return null;
}
@@ -2252,22 +2132,31 @@
*
* @param values the row values to render
* @param start the absolute start index
+ * @param isRebuildingAllRows is this going to rebuild all rows
* @return a {@link SafeHtml} string containing the row values
*/
- private SafeHtml buildRowValues(List<T> values, int start) {
- UtilityImpl utility = new UtilityImpl();
+ private SafeHtml buildRowValues(List<T> values, int start, boolean
isRebuildingAllRows) {
int length = values.size();
int end = start + length;
+ tableBuilder.start(isRebuildingAllRows);
for (int i = start; i < end; i++) {
T value = values.get(i - start);
- utility.setRowInfo(i, value);
- tableBuilder.buildRow(value, i, utility);
+ tableBuilder.buildRow(value, i);
}
// Update the properties of the table.
coalesceCellProperties();
-
- return utility.asSafeHtml();
+ 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");
+ }
}
/**
@@ -2290,7 +2179,7 @@
dependsOnSelection = false;
handlesSelection = false;
isInteractive = false;
- for (HasCell<T, ?> column : idToCellMap.values()) {
+ for (HasCell<T, ?> column : tableBuilder.getColumns()) {
Cell<?> cell = column.getCell();
if (cell.dependsOnSelection()) {
dependsOnSelection = true;
@@ -2495,7 +2384,7 @@
* (including the tabIndex that we set) could be modified by its Cell.
We
* return the TD to be safe.
*/
- if (isCellParent(td) != null) {
+ if (tableBuilder.isColumn(td)) {
return td;
}
@@ -2540,23 +2429,7 @@
}
return null;
}
-
- /**
- * Get the index of the row value from the associated {@link
TableRowElement}.
- *
- * @param row the row element
- * @return the row value index
- */
- private int getRowValueIndex(TableRowElement row) {
- try {
- return Integer.parseInt(row.getAttribute(ROW_ATTRIBUTE));
- } catch (NumberFormatException e) {
- // The attribute doesn't exist. Maybe the user is overriding
- // renderRowValues().
- return row.getSectionRowIndex() + getPageStart();
- }
- }
-
+
/**
* Get the {@link IconCellDecorator} used to decorate sorted column
headers.
*
@@ -2578,24 +2451,6 @@
return sortDescDecorator;
}
}
-
- /**
- * Get the index of the subrow value from the associated
- * {@link TableRowElement}. The sub row value starts at 0 for the first
row
- * that represents a row value.
- *
- * @param row the row element
- * @return the subrow value index, or 0 if not found
- */
- private int getSubrowValueIndex(TableRowElement row) {
- try {
- return Integer.parseInt(row.getAttribute(SUBROW_ATTRIBUTE));
- } catch (NumberFormatException e) {
- // The attribute doesn't exist. Maybe the user is overriding
- // renderRowValues().
- return 0;
- }
- }
/**
* Initialize the widget.
@@ -2620,20 +2475,6 @@
// Set the keyboard handler.
setKeyboardSelectionHandler(new
CellTableKeyboardSelectionHandler<T>(this));
}
-
- /**
- * Check if an element is the parent of a rendered cell.
- *
- * @param elem the element to check
- * @return the cellId if a cell parent, null if not
- */
- private String isCellParent(Element elem) {
- if (elem == null) {
- return null;
- }
- String cellId = elem.getAttribute(CELL_ATTRIBUTE);
- return (cellId == null) || (cellId.length() == 0) ? null : cellId;
- }
/**
* Mark the column widths as dirty and redraw the table.
@@ -2664,13 +2505,10 @@
}
}
- private <C> boolean resetFocusOnCellImpl(int row, int col, HasCell<T, C>
column,
+ private <C> boolean resetFocusOnCellImpl(Context context, T value,
HasCell<T, C> column,
Element cellParent) {
- T value = getVisibleItem(row);
- Object key = getValueKey(value);
C cellValue = column.getValue(value);
Cell<C> cell = column.getCell();
- Context context = new Context(row + getPageStart(), col, key);
return cell.resetFocus(context, cellParent, cellValue);
}
@@ -2707,3 +2545,4 @@
}
}
}
+
=======================================
---
/trunk/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
Thu Jul 28 04:03:54 2011
+++
/trunk/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
Thu Aug 18 07:20:07 2011
@@ -17,8 +17,11 @@
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.HasCell;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableRowElement;
+
+import java.util.Collection;
/**
* Builder used to construct a CellTable.
@@ -28,63 +31,66 @@
public interface CellTableBuilder<T> {
/**
- * Utility to help build a table.
- *
- * @param <T> the row data type
+ * Build zero or more table rows for the specified row value.
+ *
+ * @param rowValue the value for the row to render
+ * @param absRowIndex the absolute row index
*/
- abstract static class Utility<T> {
-
- /**
- * Only instantiable by CellTable implementation.
- */
- Utility() {
- }
-
- /**
- * Create a {@link Context} object for the specific column index that
can be
- * passed to a Cell.
- *
- * @param column the column index of the context
- * @return a {@link Context} object
- */
- public abstract Context createContext(int column);
-
- /**
- * Render a Cell into the specified {@link ElementBuilderBase}. Use
this
- * method to ensure that the Cell Widget properly handles events
originating
- * in the Cell.
- *
- * <p>
- * The {@link ElementBuilderBase} must be in a state where attributes
and
- * html can be appended. If the builder already contains a child
element,
- * this method will fail.
- * </p>
- *
- * @param <C> the data type of the cell
- * @param builder the {@link ElementBuilderBase} to render into
- * @param context the {@link Context} of the cell
- * @param column the column or {@link HasCell} to render
- * @param rowValue the row value to render
- * @see #createContext(int)
- */
- public abstract <C> void renderCell(ElementBuilderBase<?> builder,
Context context,
- HasCell<T, C> column, T rowValue);
-
- /**
- * Add a row to the table.
- *
- * @return the row to add
- */
- public abstract TableRowBuilder startRow();
- }
+ void buildRow(T rowValue, int absRowIndex);
+
+ /**
+ * Finish the building of rows and return the table section builder.
Currently only
+ * {@link HtmlTableSectionBuilder} and its subclasses are supported.
+ */
+ TableSectionBuilder finish();
+
+ /**
+ * Return the column containing an element.
+ *
+ * @param context the context for the element
+ * @param rowValue the value for the row corresponding to the element
+ * @param elm the elm that the column contains
+ * @return the immediate column containing the element
+ */
+ HasCell<T, ?> getColumn(Context context, T rowValue, Element elem);
/**
- * Build zero or more table rows for the specified row value using the
- * {@link Utility}.
+ * Return all the columns that this table builder has renderred.
+ */
+ Collection<HasCell<T, ?>> getColumns();
+
+ /**
+ * Get the index of the primary row from the associated {@link
TableRowElement} (an TR element).
*
- * @param rowValue the value for the row to render
- * @param absRowIndex the absolute row index
- * @param utility the utility used to build the table
+ * @param row the row element
+ * @return the row value index
*/
- void buildRow(T rowValue, int absRowIndex, Utility<T> utility);
-}
+ int getRowValueIndex(TableRowElement row);
+
+ /**
+ * Get the index of the subrow value from the associated
+ * {@link TableRowElement} (an TR element). The sub row value starts at
0 for the first row
+ * that represents a row value.
+ *
+ * @param row the row element
+ * @return the subrow value index, or 0 if not found
+ */
+ int getSubrowValueIndex(TableRowElement row);
+
+ /**
+ * Return if an element contains a cell. This may be faster to execute
than {@link getColumn}.
+ *
+ * @param elem the element of interest
+ */
+ boolean isColumn(Element elem);
+
+ /**
+ * Start building rows. User may want to reset the internal state of the
table builder (e.g.,
+ * reset the internal table section builder). A flag isRebuildingAllRows
is used to mark whether
+ * the builder is going to rebuild all rows. User may want to have
different reset logic given
+ * this flag.
+ *
+ * @param isRebuildingAllRows is this start intended for rebuilding all
rows
+ */
+ void start(boolean isRebuildingAllRows);
+}
=======================================
---
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
Fri Aug 12 07:07:12 2011
+++
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
Thu Aug 18 07:20:07 2011
@@ -110,16 +110,15 @@
CellTableBuilder<String> builder =
new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
@Override
- public void buildRow(String rowValue, int absRowIndex,
- CellTableBuilder.Utility<String> utility) {
+ public void buildRowImpl(String rowValue, int absRowIndex) {
builtRows.add(absRowIndex);
- TableRowBuilder tr = utility.startRow();
+ TableRowBuilder tr = startRow();
tr.endTR(); // End the tr.
tr.end(); // Accidentally end the table section.
// Try to start another row.
try {
- utility.startRow();
+ startRow();
fail("Expected IllegalStateException: tbody is already
ended");
} catch (IllegalStateException e) {
// Expected.
@@ -143,14 +142,13 @@
CellTableBuilder<String> builder =
new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
@Override
- public void buildRow(String rowValue, int absRowIndex,
- CellTableBuilder.Utility<String> utility) {
- super.buildRow(rowValue, absRowIndex, utility);
+ public void buildRowImpl(String rowValue, int absRowIndex) {
+ super.buildRowImpl(rowValue, absRowIndex);
// Add child rows to row five.
if (absRowIndex == 5) {
for (int i = 0; i < 4; i++) {
- TableRowBuilder tr = utility.startRow();
+ TableRowBuilder tr = startRow();
tr.startTD().colSpan(2).text("child " + i).endTD();
tr.endTR();
}
@@ -188,11 +186,10 @@
CellTableBuilder<String> builder =
new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
@Override
- public void buildRow(String rowValue, int absRowIndex,
- CellTableBuilder.Utility<String> utility) {
+ public void buildRowImpl(String rowValue, int absRowIndex) {
// Skip row index 5.
if (absRowIndex != 5) {
- super.buildRow(rowValue, absRowIndex, utility);
+ super.buildRowImpl(rowValue, absRowIndex);
}
}
};
@@ -655,13 +652,12 @@
CellTableBuilder<String> builder =
new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
@Override
- public void buildRow(String rowValue, int absRowIndex,
- CellTableBuilder.Utility<String> utility) {
- super.buildRow(rowValue, absRowIndex, utility);
+ public void buildRowImpl(String rowValue, int absRowIndex) {
+ super.buildRowImpl(rowValue, absRowIndex);
// Add some children.
for (int i = 0; i < 4; i++) {
- TableRowBuilder tr = utility.startRow();
+ TableRowBuilder tr = startRow();
tr.startTD().colSpan(2).text("child " + absRowIndex + ":" +
i).endTD();
tr.endTR();
}
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors