In IE, AbstractCellTable replaces a table section by first removing existing tr
elements, and then adding new tr elements one by one. This could really slow
down rendering performance especially if there are many rows to remove and many
rows to add (e.g., 500 rows in one table page, and go to next page).
This change introduces a faster rendering approach where the new html is set to a new
DIV element (that's not attached to the body). Next the entire table section is
swapped with the existing table section. A new interface
TableSectionChangeHandler is introduced so that the subclasses (e.g., CellTable,
DataGrid) can respond to the section swapping event and update their internal
reference to the sections. This is critical since they're usually responsible
for returning the reference for AbstractCellTable to locate the correct table
sections.
When tested in IE9 with IE7 emulate mode, the time to replace 100 rows with 400
new rows in a 30 columns table reduced from 1.4s to 200ms. Tests in other IE
versions return similar results.
Review by: jlabanca@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10690 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 4804345..58dbc9d 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
@@ -384,6 +384,37 @@
String widget();
}
+ /**
+ * Interface that this class's subclass may implement to get notified with table section change
+ * event. During rendering, a faster method based on swaping the entire section will be used iff
+ * <li> it's in IE - since all other optimizations have been turned off
+ * <li> the table implements TableSectionChangeHandler interface
+ * When a section is being replaced by another table with the new table html, the methods in this
+ * interface will be invoked with the changed section. The table should update its internal
+ * references to the sections properly so that when {@link #getTableBodyElement},
+ * {@link #getTableHeadElement}, or {@link #getTableFootElement} are called, the correct section
+ * will be returned.
+ */
+ protected interface TableSectionChangeHandler {
+ /**
+ * Notify that a table body section has been changed.
+ * @param newTBody the new body section
+ */
+ void onTableBodyChange(TableSectionElement newTBody);
+
+ /**
+ * Notify that a table body section has been changed.
+ * @param newTFoot the new foot section
+ */
+ void onTableFootChange(TableSectionElement newTFoot);
+
+ /**
+ * Notify that a table head section has been changed.
+ * @param newTHead the new head section
+ */
+ void onTableHeadChange(TableSectionElement newTHead);
+ }
+
interface Template extends SafeHtmlTemplates {
@SafeHtmlTemplates.Template("<div style=\"outline:none;\">{0}</div>")
SafeHtml div(SafeHtml contents);
@@ -647,7 +678,7 @@
private static class ImplTrident extends Impl {
/**
- * Detaching a tbody in IE throws an error.
+ * A different optimization is used in IE.
*/
@Override
protected void detachSectionElement(TableSectionElement section) {
@@ -661,13 +692,25 @@
}
/**
- * IE doesn't support innerHTML on tbody, nor does it support removing or
- * replacing a tbody. The only solution is to remove and replace the rows
- * themselves.
+ * Instead of replacing each TR element, swaping out the entire section is much faster. If
+ * the table has a sectionChangeHandler, this method will be used.
*/
@Override
protected void replaceAllRowsImpl(AbstractCellTable<?> table, TableSectionElement section,
SafeHtml html) {
+ if (table instanceof TableSectionChangeHandler) {
+ replaceTableSection(table, section, html);
+ } else {
+ replaceAllRowsImplLegacy(table, section, html);
+ }
+ }
+
+ /**
+ * This method is used for legacy AbstractCellTable that's not a
+ * {@link TableSectionChangeHandler}.
+ */
+ protected void replaceAllRowsImplLegacy(AbstractCellTable<?> table, TableSectionElement section,
+ SafeHtml html) {
// Remove all children.
Element child = section.getFirstChildElement();
while (child != null) {
@@ -685,6 +728,30 @@
child = next;
}
}
+
+ /**
+ * Render html into a table section. This is achieved by first setting the html in a DIV
+ * element, and then swap the table section with the corresponding element in the DIV. This
+ * method is used in IE since the normal optimizations are not feasible.
+ *
+ * @param table the {@link AbstractCellTable}
+ * @param section the {@link TableSectionElement} to replace
+ * @param html the html of a table section element containing the rows
+ */
+ private void replaceTableSection(AbstractCellTable<?> table, TableSectionElement section,
+ SafeHtml html) {
+ String sectionName = section.getTagName().toLowerCase();
+ TableSectionElement newSection = convertToSectionElement(table, sectionName, html);
+ TableElement tableElement = table.getElement().cast();
+ tableElement.replaceChild(newSection, section);
+ if ("tbody".equals(sectionName)) {
+ ((TableSectionChangeHandler) table).onTableBodyChange(newSection);
+ } else if ("thead".equals(sectionName)) {
+ ((TableSectionChangeHandler) table).onTableHeadChange(newSection);
+ } else if ("tfoot".equals(sectionName)) {
+ ((TableSectionChangeHandler) table).onTableFootChange(newSection);
+ }
+ }
}
/**
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.java b/user/src/com/google/gwt/user/cellview/client/CellTable.java
index c9dc19e..12b3db2 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -84,7 +84,8 @@
*
* @param <T> the data type of each row
*/
-public class CellTable<T> extends AbstractCellTable<T> {
+public class CellTable<T> extends AbstractCellTable<T> implements
+ AbstractCellTable.TableSectionChangeHandler {
/**
* Resources that match the GWT standard style theme.
@@ -494,11 +495,11 @@
private final Style style;
private final TableElement table;
- private final TableSectionElement tbody;
+ private TableSectionElement tbody;
private final TableSectionElement tbodyLoading;
private final TableCellElement tbodyLoadingCell;
- private final TableSectionElement tfoot;
- private final TableSectionElement thead;
+ private TableSectionElement tfoot;
+ private TableSectionElement thead;
private boolean colGroupEnabled = true;
/**
@@ -690,6 +691,21 @@
}
@Override
+ public void onTableBodyChange(TableSectionElement newTBody) {
+ tbody = newTBody;
+ }
+
+ @Override
+ public void onTableFootChange(TableSectionElement newTFoot) {
+ tfoot = newTFoot;
+ }
+
+ @Override
+ public void onTableHeadChange(TableSectionElement newTHead) {
+ thead = newTHead;
+ }
+
+ @Override
public void removeColumnStyleName(int index, String styleName) {
assertColumnGroupEnabled("Cannot remove column style when colgroup is disabled");
if (index >= colgroup.getChildCount()) {
diff --git a/user/src/com/google/gwt/user/cellview/client/DataGrid.java b/user/src/com/google/gwt/user/cellview/client/DataGrid.java
index d771ce7..dc28b39 100644
--- a/user/src/com/google/gwt/user/cellview/client/DataGrid.java
+++ b/user/src/com/google/gwt/user/cellview/client/DataGrid.java
@@ -78,7 +78,8 @@
*
* @param <T> the data type of each row
*/
-public class DataGrid<T> extends AbstractCellTable<T> implements RequiresResize {
+public class DataGrid<T> extends AbstractCellTable<T> implements RequiresResize,
+ AbstractCellTable.TableSectionChangeHandler {
/**
* A ClientBundle that provides images for this widget.
@@ -707,6 +708,21 @@
}
@Override
+ public void onTableBodyChange(TableSectionElement newTBody) {
+ tableData.section = newTBody;
+ }
+
+ @Override
+ public void onTableFootChange(TableSectionElement newTFoot) {
+ tableFooter.section = newTFoot;
+ }
+
+ @Override
+ public void onTableHeadChange(TableSectionElement newTHead) {
+ tableHeader.section = newTHead;
+ }
+
+ @Override
public void removeColumnStyleName(int index, String styleName) {
tableHeader.removeColumnStyleName(index, styleName);
tableFooter.removeColumnStyleName(index, styleName);