Revision: 10522
Author: [email protected]
Date: Fri Aug 12 10:19:01 2011
Log: Adding configuration booleans to CellTable to refresh headers and
footers only when needed. Currently, the headers and footers are refreshed
every time the data is redrawn, even though headers and footers are often
static or do not depend on the data. Users can now call
#setAutoHeader/FooterRefreshDisabled() to disable this feature. Headers
and footers will still be redrawn when a column is inserted or removed, but
they will not be redrawn on every data update. Users can force the
headers/footers to redraw synchronously by calling #redrawHeaders/Footers.
Review at http://gwt-code-reviews.appspot.com/1520803
Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=10522
Modified:
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwDataGrid.java
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
=======================================
---
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
Wed Jul 27 04:19:13 2011
+++
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
Fri Aug 12 10:19:01 2011
@@ -130,6 +130,10 @@
ContactDatabase.ContactInfo.KEY_PROVIDER);
cellTable.setWidth("100%", true);
+ // Do not refresh the headers and footers every time the data is
updated.
+ cellTable.setAutoHeaderRefreshDisabled(true);
+ cellTable.setAutoFooterRefreshDisabled(true);
+
// Attach a column sort handler to the ListDataProvider to sort the
list.
ListHandler<ContactInfo> sortHandler = new ListHandler<ContactInfo>(
ContactDatabase.get().getDataProvider().getList());
@@ -163,10 +167,12 @@
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
GWT.runAsync(CwCellTable.class, new RunAsyncCallback() {
+ @Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
+ @Override
public void onSuccess() {
callback.onSuccess(onInitialize());
}
@@ -204,12 +210,14 @@
};
firstNameColumn.setSortable(true);
sortHandler.setComparator(firstNameColumn, new
Comparator<ContactInfo>() {
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getFirstName().compareTo(o2.getFirstName());
}
});
cellTable.addColumn(firstNameColumn,
constants.cwCellTableColumnFirstName());
firstNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo,
String>() {
+ @Override
public void update(int index, ContactInfo object, String value) {
// Called when the user changes the value.
object.setFirstName(value);
@@ -228,12 +236,14 @@
};
lastNameColumn.setSortable(true);
sortHandler.setComparator(lastNameColumn, new
Comparator<ContactInfo>() {
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getLastName().compareTo(o2.getLastName());
}
});
cellTable.addColumn(lastNameColumn,
constants.cwCellTableColumnLastName());
lastNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>()
{
+ @Override
public void update(int index, ContactInfo object, String value) {
// Called when the user changes the value.
object.setLastName(value);
@@ -258,6 +268,7 @@
};
cellTable.addColumn(categoryColumn,
constants.cwCellTableColumnCategory());
categoryColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>()
{
+ @Override
public void update(int index, ContactInfo object, String value) {
for (Category category : categories) {
if (category.getDisplayName().equals(value)) {
@@ -280,6 +291,7 @@
addressColumn.setSortable(true);
addressColumn.setDefaultSortAscending(false);
sortHandler.setComparator(addressColumn, new Comparator<ContactInfo>()
{
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getAddress().compareTo(o2.getAddress());
}
=======================================
---
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
Thu Jul 28 04:03:54 2011
+++
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
Fri Aug 12 10:19:01 2011
@@ -549,12 +549,21 @@
// Create a DataGrid.
- // Set a key provider that provides a unique key for each contact. If
key is
- // used to identify contacts when fields (such as the name and address)
- // change.
+ /*
+ * Set a key provider that provides a unique key for each contact. If
key is
+ * used to identify contacts when fields (such as the name and address)
+ * change.
+ */
dataGrid = new
DataGrid<ContactInfo>(ContactDatabase.ContactInfo.KEY_PROVIDER);
dataGrid.setWidth("100%");
+ /*
+ * Do not refresh the headers every time the data is updated. The
footer
+ * depends on the current data, so we do not disable auto refresh on
the
+ * footer.
+ */
+ dataGrid.setAutoHeaderRefreshDisabled(true);
+
// Set the message to display when the table is empty.
dataGrid.setEmptyTableWidget(new
Label(constants.cwCustomDataGridEmpty()));
=======================================
---
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwDataGrid.java
Thu May 26 04:44:13 2011
+++
/trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwDataGrid.java
Fri Aug 12 10:19:01 2011
@@ -136,12 +136,21 @@
public Widget onInitialize() {
// Create a DataGrid.
- // Set a key provider that provides a unique key for each contact. If
key is
- // used to identify contacts when fields (such as the name and address)
- // change.
+ /*
+ * Set a key provider that provides a unique key for each contact. If
key is
+ * used to identify contacts when fields (such as the name and address)
+ * change.
+ */
dataGrid = new
DataGrid<ContactInfo>(ContactDatabase.ContactInfo.KEY_PROVIDER);
dataGrid.setWidth("100%");
+ /*
+ * Do not refresh the headers every time the data is updated. The
footer
+ * depends on the current data, so we do not disable auto refresh on
the
+ * footer.
+ */
+ dataGrid.setAutoHeaderRefreshDisabled(true);
+
// Set the message to display when the table is empty.
dataGrid.setEmptyTableWidget(new Label(constants.cwDataGridEmpty()));
@@ -176,10 +185,12 @@
protected void asyncOnInitialize(final AsyncCallback<Widget> callback) {
GWT.runAsync(CwDataGrid.class, new RunAsyncCallback() {
+ @Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
+ @Override
public void onSuccess() {
callback.onSuccess(onInitialize());
}
@@ -216,12 +227,14 @@
};
firstNameColumn.setSortable(true);
sortHandler.setComparator(firstNameColumn, new
Comparator<ContactInfo>() {
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getFirstName().compareTo(o2.getFirstName());
}
});
dataGrid.addColumn(firstNameColumn,
constants.cwDataGridColumnFirstName());
firstNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo,
String>() {
+ @Override
public void update(int index, ContactInfo object, String value) {
// Called when the user changes the value.
object.setFirstName(value);
@@ -240,12 +253,14 @@
};
lastNameColumn.setSortable(true);
sortHandler.setComparator(lastNameColumn, new
Comparator<ContactInfo>() {
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getLastName().compareTo(o2.getLastName());
}
});
dataGrid.addColumn(lastNameColumn,
constants.cwDataGridColumnLastName());
lastNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>()
{
+ @Override
public void update(int index, ContactInfo object, String value) {
// Called when the user changes the value.
object.setLastName(value);
@@ -263,6 +278,7 @@
};
lastNameColumn.setSortable(true);
sortHandler.setComparator(ageColumn, new Comparator<ContactInfo>() {
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getBirthday().compareTo(o2.getBirthday());
}
@@ -301,6 +317,7 @@
};
dataGrid.addColumn(categoryColumn,
constants.cwDataGridColumnCategory());
categoryColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>()
{
+ @Override
public void update(int index, ContactInfo object, String value) {
for (Category category : categories) {
if (category.getDisplayName().equals(value)) {
@@ -321,6 +338,7 @@
};
addressColumn.setSortable(true);
sortHandler.setComparator(addressColumn, new Comparator<ContactInfo>()
{
+ @Override
public int compare(ContactInfo o1, ContactInfo o2) {
return o1.getAddress().compareTo(o2.getAddress());
}
=======================================
---
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
Thu Aug 11 03:52:26 2011
+++
/trunk/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
Fri Aug 12 10:19:01 2011
@@ -961,6 +961,7 @@
private boolean dependsOnSelection;
private Widget emptyTableWidget;
+ private boolean footerRefreshDisabled;
private final List<Header<?>> footers = new ArrayList<Header<?>>();
/**
@@ -968,8 +969,15 @@
*/
private boolean handlesSelection;
+ private boolean headerRefreshDisabled;
private final List<Header<?>> headers = new ArrayList<Header<?>>();
+ /**
+ * Indicates that either the headers or footers are dirty, and both
should be
+ * refreshed the next time the table is redrawn.
+ */
+ private boolean headersDirty;
+
private TableRowElement hoveringRow;
/**
@@ -1200,6 +1208,11 @@
* Modifications to the {@link ColumnSortList} will be reflected in the
table
* header.
*
+ * <p>
+ * Note that the implementation may redraw the headers on every
modification
+ * to the {@link ColumnSortList}.
+ * </p>
+ *
* @return the {@link ColumnSortList}
*/
public ColumnSortList getColumnSortList() {
@@ -1363,6 +1376,7 @@
}
CellBasedWidgetImpl.get().sinkEvents(this, consumedEvents);
+ headersDirty = true;
refreshColumnsAndRedraw();
}
@@ -1419,14 +1433,34 @@
}
/**
- * Redraw the table's footers.
+ * Check if auto footer refresh is enabled or disabled
+ *
+ * @return true if disabled, false if enabled
+ * @see #setAutoFooterRefreshDisabled(boolean)
+ */
+ public boolean isAutoFooterRefreshDisabled() {
+ return footerRefreshDisabled;
+ }
+
+ /**
+ * Check if auto header refresh is enabled or disabled
+ *
+ * @return true if disabled, false if enabled
+ * @see #setAutoHeaderRefreshDisabled(boolean)
+ */
+ public boolean isAutoHeaderRefreshDisabled() {
+ return headerRefreshDisabled;
+ }
+
+ /**
+ * Redraw the table's footers. The footers will be re-rendered
synchronously.
*/
public void redrawFooters() {
createHeaders(true);
}
/**
- * Redraw the table's headers.
+ * Redraw the table's headers. The headers will be re-rendered
synchronously.
*/
public void redrawHeaders() {
createHeaders(false);
@@ -1464,6 +1498,7 @@
}
// Redraw the table asynchronously.
+ headersDirty = true;
refreshColumnsAndRedraw();
// We don't unsink events because other handlers or user code may have
sunk
@@ -1477,6 +1512,36 @@
* @param styleName the style name to remove
*/
public abstract void removeColumnStyleName(int index, String styleName);
+
+ /**
+ * Enable or disable auto footer refresh when row data is changed. By
default,
+ * footers are refreshed every time the row data changes in case the
headers
+ * depend on the current row data. If the headers do not depend on the
current
+ * row data, you can disable this feature to improve performance.
+ *
+ * <p>
+ * Note that headers will still refresh when columns are added or
removed,
+ * regardless of whether or not this feature is enabled.
+ * </p>
+ */
+ public void setAutoFooterRefreshDisabled(boolean disabled) {
+ this.footerRefreshDisabled = disabled;
+ }
+
+ /**
+ * Enable or disable auto header refresh when row data is changed. By
default,
+ * headers are refreshed every time the row data changes in case the
footers
+ * depend on the current row data. If the footers do not depend on the
current
+ * row data, you can disable this feature to improve performance.
+ *
+ * <p>
+ * Note that footers will still refresh when columns are added or
removed,
+ * regardless of whether or not this feature is enabled.
+ * </p>
+ */
+ public void setAutoHeaderRefreshDisabled(boolean disabled) {
+ this.headerRefreshDisabled = disabled;
+ }
/**
* Set the width of a {@link Column}. The width will persist with the
column
@@ -1795,6 +1860,11 @@
// TODO(jlabanca): Get visible col when custom headers are
supported.
Column<T, ?> column = col < columns.size() ? columns.get(col) :
null;
if (column != null && column.isSortable()) {
+ /*
+ * Force the headers to refresh the next time data is pushed
so we
+ * update the sort icon in the header.
+ */
+ headersDirty = true;
updatingSortList = true;
sortList.push(column);
updatingSortList = false;
@@ -2362,11 +2432,6 @@
// If the section isn't used, hide it.
doSetHeaderVisible(isFooter, hasHeader);
}
-
- private void createHeadersAndFooters() {
- createHeaders(false);
- createHeaders(true);
- }
/**
* Fire an event to the Cell within the specified {@link
TableCellElement}.
@@ -2589,7 +2654,14 @@
}
// Render the headers and footers.
- createHeadersAndFooters();
+ boolean wereHeadersDirty = headersDirty;
+ headersDirty = false;
+ if (wereHeadersDirty || !headerRefreshDisabled) {
+ createHeaders(false);
+ }
+ if (wereHeadersDirty || !footerRefreshDisabled) {
+ createHeaders(true);
+ }
}
private <C> boolean resetFocusOnCellImpl(int row, int col, HasCell<T, C>
column,
=======================================
---
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
Thu Jul 28 04:03:54 2011
+++
/trunk/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
Fri Aug 12 07:07:12 2011
@@ -746,6 +746,194 @@
// Expected.
}
}
+
+ public void testSetAutoFooterRefreshDisabled() {
+ AbstractCellTable<String> table = createAbstractHasData();
+ assertFalse(table.isAutoHeaderRefreshDisabled());
+ assertFalse(table.isAutoFooterRefreshDisabled());
+
+ table.setAutoFooterRefreshDisabled(true);
+ assertFalse(table.isAutoHeaderRefreshDisabled());
+ assertTrue(table.isAutoFooterRefreshDisabled());
+
+ /*
+ * Inserting a column should render the headers and footers, even if
auto
+ * refresh is disabled.
+ */
+ final List<String> log = new ArrayList<String>();
+ Column<String, ?> col0 = new MockColumn<String, String>();
+ TextHeader header0 = new TextHeader("header0") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("header0 rendered");
+ }
+ };
+ TextHeader footer0 = new TextHeader("footer0") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("footer0 rendered");
+ }
+ };
+ table.addColumn(col0, header0, footer0);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header0 rendered", log.remove(0));
+ assertEquals("footer0 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Inserting another column should render the headers and footers,
even if
+ * auto refresh is disabled.
+ */
+ Column<String, ?> col1 = new MockColumn<String, String>();
+ TextHeader header1 = new TextHeader("header1") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("header1 rendered");
+ }
+ };
+ TextHeader footer1 = new TextHeader("footer1") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("footer1 rendered");
+ }
+ };
+ table.addColumn(col1, header1, footer1);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header0 rendered", log.remove(0));
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals("footer0 rendered", log.remove(0));
+ assertEquals("footer1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Removing a column should render the headers and footers, even if
auto
+ * refresh is disabled.
+ */
+ table.removeColumn(col0);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals("footer1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Setting data only causes footers to render if auto refresh is
enabled,
+ * which it is not. Header refresh is still enabled.
+ */
+ populateData(table);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Sorting a column forces the headers only to refresh. The footers
are not
+ * refreshed.
+ */
+ table.getColumnSortList().push(col1);
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+ }
+
+ public void testSetAutoHeaderRefreshDisabled() {
+ AbstractCellTable<String> table = createAbstractHasData();
+ assertFalse(table.isAutoHeaderRefreshDisabled());
+ assertFalse(table.isAutoFooterRefreshDisabled());
+
+ table.setAutoHeaderRefreshDisabled(true);
+ assertTrue(table.isAutoHeaderRefreshDisabled());
+ assertFalse(table.isAutoFooterRefreshDisabled());
+
+ /*
+ * Inserting a column should render the headers and footers, even if
auto
+ * refresh is disabled.
+ */
+ final List<String> log = new ArrayList<String>();
+ Column<String, ?> col0 = new MockColumn<String, String>();
+ TextHeader header0 = new TextHeader("header0") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("header0 rendered");
+ }
+ };
+ TextHeader footer0 = new TextHeader("footer0") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("footer0 rendered");
+ }
+ };
+ table.addColumn(col0, header0, footer0);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header0 rendered", log.remove(0));
+ assertEquals("footer0 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Inserting another column should render the headers and footers,
even if
+ * auto refresh is disabled.
+ */
+ Column<String, ?> col1 = new MockColumn<String, String>();
+ TextHeader header1 = new TextHeader("header1") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("header1 rendered");
+ }
+ };
+ TextHeader footer1 = new TextHeader("footer1") {
+ @Override
+ public void render(Context context, SafeHtmlBuilder sb) {
+ super.render(context, sb);
+ log.add("footer1 rendered");
+ }
+ };
+ table.addColumn(col1, header1, footer1);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header0 rendered", log.remove(0));
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals("footer0 rendered", log.remove(0));
+ assertEquals("footer1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Removing a column should render the headers and footers, even if
auto
+ * refresh is disabled.
+ */
+ table.removeColumn(col0);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals("footer1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Setting data only causes headers to render if auto refresh is
enabled,
+ * which it is not. Footer refresh is still enabled.
+ */
+ populateData(table);
+ assertEquals(0, log.size()); // Headers are rendered asynchronously.
+ table.getPresenter().flush(); // Force headers to render.
+ assertEquals("footer1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+
+ /*
+ * Sorting a column forces the headers only to refresh. The footers
are not
+ * refreshed.
+ */
+ table.getColumnSortList().push(col1);
+ assertEquals("header1 rendered", log.remove(0));
+ assertEquals(0, log.size());
+ }
public void testSetColumnWidth() {
AbstractCellTable<String> table = createAbstractHasData(new
TextCell());
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors