Adding a LoadingStateEvent to CellList and CellTable so users can receive an event when the data is loaded. Previously, LoadingState was a package protected enum used to show the loading indicator in CellTable and empty list message in CellList. Now, LoadingState is a first class citizen (an interface so users can define their own loading state). Both CellList and CellTable now support setting a custom loading indicator Widget and a custom Widget to display when the table is empty. Review at http://gwt-code-reviews.appspot.com/1338809 Review by: pdr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9687 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java index 7275c1a..6c94271 100644 --- a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java +++ b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
@@ -20,6 +20,7 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; @@ -28,7 +29,7 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Focusable; @@ -125,7 +126,7 @@ public void setLoadingState(LoadingState state) { hasData.isRefreshing = true; - hasData.setLoadingState(state); + hasData.onLoadingStateChanged(state); hasData.isRefreshing = false; } @@ -136,8 +137,7 @@ // Use an anonymous class to override ValueChangeEvents's protected // constructor. We can't call ValueChangeEvent.fire() because this class // doesn't implement HasValueChangeHandlers. - hasData.fireEvent(new ValueChangeEvent<List<T>>( - hasData.getVisibleItems()) { + hasData.fireEvent(new ValueChangeEvent<List<T>>(hasData.getVisibleItems()) { }); } } @@ -279,8 +279,7 @@ CellBasedWidgetImpl.get().sinkEvents(this, eventTypes); // Add a default selection event manager. - selectionManagerReg = addCellPreviewHandler( - DefaultSelectionEventManager.<T> createDefaultManager()); + selectionManagerReg = addCellPreviewHandler(DefaultSelectionEventManager.<T> createDefaultManager()); } public HandlerRegistration addCellPreviewHandler( @@ -288,6 +287,18 @@ return presenter.addCellPreviewHandler(handler); } + /** + * Add a {@link LoadingStateChangeEvent.Handler} to be notified of changes in + * the loading state. + * + * @param handler the handle + * @return the registration for the handler + */ + public HandlerRegistration addLoadingStateChangeHandler( + LoadingStateChangeEvent.Handler handler) { + return presenter.addLoadingStateChangeHandler(handler); + } + public HandlerRegistration addRangeChangeHandler( RangeChangeEvent.Handler handler) { return presenter.addRangeChangeHandler(handler); @@ -750,6 +761,16 @@ protected void onFocus() { } + /** + * Called when the loading state changes. By default, this implementation + * fires a {@link LoadingStateChangeEvent}. + * + * @param state the new loading state + */ + protected void onLoadingStateChanged(LoadingState state) { + fireEvent(new LoadingStateChangeEvent(state)); + } + @Override protected void onUnload() { isFocused = false; @@ -870,15 +891,51 @@ return addHandler(handler, ValueChangeEvent.getType()); } + /** + * Adopt the specified widget. + * + * @param child the child to adopt + */ + native void adopt(Widget child) /*-{ + child.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(this); + }-*/; + + /** + * Attach a child. + * + * @param child the child to attach + */ + native void doAttach(Widget child) /*-{ + child.@com.google.gwt.user.client.ui.Widget::onAttach()(); + }-*/; + + /** + * Detach a child. + * + * @param child the child to detach + */ + native void doDetach(Widget child) /*-{ + child.@com.google.gwt.user.client.ui.Widget::onDetach()(); + }-*/; + HasDataPresenter<T> getPresenter() { return presenter; } /** - * Set the current loading state of the data. - * - * @param state the loading state + * Show or hide an element. + * + * @param element the element + * @param show true to show, false to hide */ - void setLoadingState(LoadingState state) { + void showOrHide(Element element, boolean show) { + if (element == null) { + return; + } + if (show) { + element.getStyle().clearDisplay(); + } else { + element.getStyle().setDisplay(Display.NONE); + } } }
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.java b/user/src/com/google/gwt/user/cellview/client/CellList.java index c7100d1..7a85a9d 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellList.java +++ b/user/src/com/google/gwt/user/cellview/client/CellList.java
@@ -1,12 +1,12 @@ /* * Copyright 2010 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 @@ -24,7 +24,6 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource.ImportedWithPrefix; @@ -35,18 +34,24 @@ 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.cellview.client.HasDataPresenter.LoadingState; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.AttachDetachException; +import com.google.gwt.user.client.ui.DeckPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.CellPreviewEvent; import com.google.gwt.view.client.ProvidesKey; import com.google.gwt.view.client.SelectionModel; +import java.util.Collections; import java.util.List; import java.util.Set; /** * A single column list of cells. - * + * * <p> * <h3>Examples</h3> * <dl> @@ -58,7 +63,7 @@ * <dd>{@example com.google.gwt.examples.view.KeyProviderExample}</dd> * </dl> * </p> - * + * * @param <T> the data type of list items */ public class CellList<T> extends AbstractHasData<T> { @@ -148,9 +153,14 @@ private final Cell<T> cell; private boolean cellIsEditing; private final Element childContainer; - private SafeHtml emptyListMessage = SafeHtmlUtils.fromSafeConstant(""); - private final Element emptyMessageElem; + private final SimplePanel emptyListWidgetContainer = new SimplePanel(); + private final SimplePanel loadingIndicatorContainer = new SimplePanel(); + + /** + * A {@link DeckPanel} to hold widgets associated with various loading states. + */ + private final DeckPanel messagesPanel = new DeckPanel(); private final Style style; @@ -158,7 +168,7 @@ /** * Construct a new {@link CellList}. - * + * * @param cell the cell used to render each item */ public CellList(final Cell<T> cell) { @@ -167,7 +177,7 @@ /** * Construct a new {@link CellList} with the specified {@link Resources}. - * + * * @param cell the cell used to render each item * @param resources the resources used for this widget */ @@ -176,26 +186,28 @@ } /** - * Construct a new {@link CellList} with the specified {@link ProvidesKey key provider}. - * + * Construct a new {@link CellList} with the specified {@link ProvidesKey key + * provider}. + * * @param cell the cell used to render each item * @param keyProvider an instance of ProvidesKey<T>, or null if the record - * object should act as its own key + * object should act as its own key */ public CellList(final Cell<T> cell, ProvidesKey<T> keyProvider) { this(cell, getDefaultResources(), keyProvider); } /** - * Construct a new {@link CellList} with the specified {@link Resources} - * and {@link ProvidesKey key provider}. - * + * Construct a new {@link CellList} with the specified {@link Resources} and + * {@link ProvidesKey key provider}. + * * @param cell the cell used to render each item * @param resources the resources used for this widget * @param keyProvider an instance of ProvidesKey<T>, or null if the record - * object should act as its own key + * object should act as its own key */ - public CellList(final Cell<T> cell, Resources resources, ProvidesKey<T> keyProvider) { + public CellList(final Cell<T> cell, Resources resources, + ProvidesKey<T> keyProvider) { super(Document.get().createDivElement(), DEFAULT_PAGE_SIZE, keyProvider); this.cell = cell; this.style = resources.cellListStyle(); @@ -207,15 +219,16 @@ addStyleName(widgetStyle); } - // Create the DOM hierarchy. + // Add the child container. childContainer = Document.get().createDivElement(); - - emptyMessageElem = Document.get().createDivElement(); - showOrHide(emptyMessageElem, false); - DivElement outerDiv = getElement().cast(); outerDiv.appendChild(childContainer); - outerDiv.appendChild(emptyMessageElem); + + // Attach the message panel. + outerDiv.appendChild(messagesPanel.getElement()); + adopt(messagesPanel); + messagesPanel.add(emptyListWidgetContainer); + messagesPanel.add(loadingIndicatorContainer); // Sink events that the cell consumes. CellBasedWidgetImpl.get().sinkEvents(this, cell.getConsumedEvents()); @@ -223,18 +236,38 @@ /** * Get the message that is displayed when there is no data. - * + * * @return the empty message * @see #setEmptyListMessage(SafeHtml) + * @deprecated as of GWT 2.3, use {@link #getEmptyListWidget()} instead */ + @Deprecated public SafeHtml getEmptyListMessage() { return emptyListMessage; } /** + * Get the widget displayed when the list has no rows. + * + * @return the empty list widget + */ + public Widget getEmptyListWidget() { + return emptyListWidgetContainer.getWidget(); + } + + /** + * Get the widget displayed when the data is loading. + * + * @return the loading indicator + */ + public Widget getLoadingIndicator() { + return loadingIndicatorContainer.getWidget(); + } + + /** * Get the {@link Element} for the specified index. If the element has not * been created, null is returned. - * + * * @param indexOnPage the index on the page * @return the element, or null if it doesn't exists * @throws IndexOutOfBoundsException if the index is outside of the current @@ -251,18 +284,40 @@ /** * Set the message to display when there is no data. - * + * * @param html the message to display when there are no results * @see #getEmptyListMessage() + * @deprecated as of GWT 2.3, use + * {@link #setEmptyDataWidget(com.google.gwt.user.client.ui.Widget)} + * instead */ + @Deprecated public void setEmptyListMessage(SafeHtml html) { this.emptyListMessage = html; - emptyMessageElem.setInnerHTML(html.asString()); + setEmptyListWidget(html == null ? null : new HTML(html)); + } + + /** + * Set the widget to display when the list has no rows. + * + * @param widget the empty data widget + */ + public void setEmptyListWidget(Widget widget) { + emptyListWidgetContainer.setWidget(widget); + } + + /** + * Set the widget to display when the data is loading. + * + * @param widget the loading indicator + */ + public void setLoadingIndicator(Widget widget) { + loadingIndicatorContainer.setWidget(widget); } /** * Set the value updater to use when cells modify items. - * + * * @param valueUpdater the {@link ValueUpdater} */ public void setValueUpdater(ValueUpdater<T> valueUpdater) { @@ -274,6 +329,24 @@ return cell.dependsOnSelection(); } + @Override + protected void doAttachChildren() { + try { + doAttach(messagesPanel); + } catch (Throwable e) { + throw new AttachDetachException(Collections.singleton(e)); + } + } + + @Override + protected void doDetachChildren() { + try { + doDetach(messagesPanel); + } catch (Throwable e) { + throw new AttachDetachException(Collections.singleton(e)); + } + } + /** * Called when a user action triggers selection. * @@ -323,7 +396,7 @@ /** * Get the parent element that wraps the cell from the list item. Override * this method if you add structure to the element. - * + * * @param item the row element that wraps the list item * @return the parent element of the cell */ @@ -429,6 +502,35 @@ } } + /** + * Called when the loading state changes. + * + * @param state the new loading state + */ + @Override + protected void onLoadingStateChanged(LoadingState state) { + Widget message = null; + if (state == LoadingState.LOADING) { + // Loading indicator. + message = loadingIndicatorContainer; + } else if (state == LoadingState.LOADED && getPresenter().isEmpty()) { + // Empty table. + message = emptyListWidgetContainer; + } + + // Switch out the message to display. + if (message != null) { + messagesPanel.showWidget(messagesPanel.getWidgetIndex(message)); + } + + // Show the correct container. + showOrHide(getChildContainer(), message == null); + messagesPanel.setVisible(message != null); + + // Fire an event. + super.onLoadingStateChanged(state); + } + @Override protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start, SelectionModel<? super T> selectionModel) { @@ -515,24 +617,4 @@ protected void setSelected(Element elem, boolean selected) { setStyleName(elem, style.cellListSelectedItem(), selected); } - - @Override - void setLoadingState(LoadingState state) { - showOrHide(emptyMessageElem, state == LoadingState.EMPTY); - // TODO(jlabanca): Add a loading icon. - } - - /** - * Show or hide an element. - * - * @param element the element - * @param show true to show, false to hide - */ - private void showOrHide(Element element, boolean show) { - if (show) { - element.getStyle().clearDisplay(); - } else { - element.getStyle().setDisplay(Display.NONE); - } - } }
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.css b/user/src/com/google/gwt/user/cellview/client/CellTable.css index 6a73c90..061aba8 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTable.css +++ b/user/src/com/google/gwt/user/cellview/client/CellTable.css
@@ -132,7 +132,6 @@ border: selectionBorderWidth solid #d7dde8; } -@sprite .cellTableLoading { - gwt-image: 'cellTableLoading'; +.cellTableLoading { margin: 30px; }
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 4da9d35..c01d333 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTable.java +++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -25,7 +25,6 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.TableLayout; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; @@ -47,11 +46,15 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo; -import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.DeckPanel; import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.CellPreviewEvent; import com.google.gwt.view.client.ProvidesKey; import com.google.gwt.view.client.SelectionModel; @@ -317,9 +320,6 @@ @Template("<div style=\"outline:none;\" tabindex=\"{0}\" accessKey=\"{1}\">{2}</div>") SafeHtml divFocusableWithKey(int tabIndex, char accessKey, SafeHtml contents); - @Template("<div class=\"{0}\"></div>") - SafeHtml loading(String loading); - @Template("<table><tbody>{0}</tbody></table>") SafeHtml tbody(SafeHtml rowHtml); @@ -493,6 +493,7 @@ */ private boolean dependsOnSelection; + private final SimplePanel emptyTableWidgetContainer = new SimplePanel(); private final List<Header<?>> footers = new ArrayList<Header<?>>(); /** @@ -510,6 +511,12 @@ private boolean isInteractive; private int keyboardSelectedColumn = 0; + private final SimplePanel loadingIndicatorContainer = new SimplePanel(); + + /** + * A {@link DeckPanel} to hold widgets associated with various loading states. + */ + private final DeckPanel messagesPanel = new DeckPanel(); private final Resources resources; private RowStyles<T> rowStyles; @@ -520,6 +527,7 @@ private final TableElement table; private final TableSectionElement tbody; private final TableSectionElement tbodyLoading; + private final TableCellElement tbodyLoadingCell; private final TableSectionElement tfoot; private final TableSectionElement thead; private boolean updatingSortList; @@ -621,17 +629,23 @@ tfoot = table.createTFoot(); setStyleName(this.style.cellTableWidget()); - // Create the loading indicator. + // Attach the messages panel. { - TableCellElement td = Document.get().createTDElement(); + tbodyLoadingCell = Document.get().createTDElement(); TableRowElement tr = Document.get().createTRElement(); tbodyLoading.appendChild(tr); - tr.appendChild(td); - td.setAlign("center"); - td.setInnerHTML(template.loading(style.cellTableLoading()).asString()); - setLoadingIconVisible(false); + tr.appendChild(tbodyLoadingCell); + tbodyLoadingCell.setAlign("center"); + tbodyLoadingCell.appendChild(messagesPanel.getElement()); + adopt(messagesPanel); + messagesPanel.add(emptyTableWidgetContainer); + messagesPanel.add(loadingIndicatorContainer); + loadingIndicatorContainer.setStyleName(style.cellTableLoading()); } + // Set the default loading indicator. + setLoadingIndicator(new Image(resources.cellTableLoading())); + // Sink events. Set<String> eventTypes = new HashSet<String>(); eventTypes.add("mouseover"); @@ -800,6 +814,15 @@ } /** + * Get the widget displayed when the table has no rows. + * + * @return the empty table widget + */ + public Widget getEmptyTableWidget() { + return emptyTableWidgetContainer.getWidget(); + } + + /** * Return the height of the table header. * * @return an int representing the header height @@ -810,6 +833,15 @@ } /** + * Get the widget displayed when the data is loading. + * + * @return the loading indicator + */ + public Widget getLoadingIndicator() { + return loadingIndicatorContainer.getWidget(); + } + + /** * Get the {@link TableRowElement} for the specified row. If the row element * has not been created, null is returned. * @@ -1066,6 +1098,24 @@ } /** + * Set the widget to display when the table has no rows. + * + * @param widget the empty table widget + */ + public void setEmptyTableWidget(Widget widget) { + emptyTableWidgetContainer.setWidget(widget); + } + + /** + * Set the widget to display when the data is loading. + * + * @param widget the loading indicator + */ + public void setLoadingIndicator(Widget widget) { + loadingIndicatorContainer.setWidget(widget); + } + + /** * Sets the object used to determine how a row is styled; the change will take * effect the next time that the table is rendered. * @@ -1322,6 +1372,38 @@ } } + /** + * Called when the loading state changes. + * + * @param state the new loading state + */ + @Override + protected void onLoadingStateChanged(LoadingState state) { + Widget message = null; + if (state == LoadingState.LOADING) { + // Loading indicator. + message = loadingIndicatorContainer; + } else if (state == LoadingState.LOADED && getPresenter().isEmpty()) { + // Empty table. + message = emptyTableWidgetContainer; + } + + // Switch out the message to display. + if (message != null) { + messagesPanel.showWidget(messagesPanel.getWidgetIndex(message)); + } + + // Adjust the colspan of the messages panel container. + tbodyLoadingCell.setColSpan(Math.max(1, columns.size())); + + // Show the correct container. + showOrHide(getChildContainer(), message == null); + showOrHide(tbodyLoading, message != null); + + // Fire an event. + super.onLoadingStateChanged(state); + } + @Override protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start, SelectionModel<? super T> selectionModel) { @@ -1501,11 +1583,6 @@ style.cellTableSelectedRowCell(), selected); } - @Override - void setLoadingState(LoadingState state) { - setLoadingIconVisible(state == LoadingState.LOADING); - } - /** * Check that the specified column is within bounds. * @@ -1889,26 +1966,6 @@ } /** - * Show or hide the loading icon. - * - * @param visible true to show, false to hide. - */ - private void setLoadingIconVisible(boolean visible) { - // Clear the current data. - if (visible) { - tbody.getStyle().setDisplay(Display.NONE); - } else { - tbody.getStyle().clearDisplay(); - } - - // Update the colspan. - TableCellElement td = tbodyLoading.getRows().getItem(0).getCells().getItem( - 0); - td.setColSpan(Math.max(1, columns.size())); - setVisible(tbodyLoading, visible); - } - - /** * Apply a style to a row and all cells in the row. * * @param tr the row element
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css b/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css index c34771c..8f98e26 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css +++ b/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
@@ -137,7 +137,6 @@ background: #d7dde8; } -@sprite .cellTableLoading { - gwt-image: 'cellTableLoading'; +.cellTableLoading { margin: 30px; }
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java index 22c0938..ae77b7c 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java +++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -37,8 +37,8 @@ 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.cellview.client.HasDataPresenter.LoadingState; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.impl.FocusImpl; import com.google.gwt.view.client.CellPreviewEvent; @@ -252,7 +252,8 @@ public void setLoadingState(LoadingState state) { nodeView.updateImage(state == LoadingState.LOADING); - showOrHide(nodeView.emptyMessageElem, state == LoadingState.EMPTY); + showOrHide(nodeView.emptyMessageElem, state == LoadingState.LOADED + && presenter.isEmpty()); } /**
diff --git a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java index ee3789c..b514593 100644 --- a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java +++ b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
@@ -23,7 +23,7 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.view.client.CellPreviewEvent; import com.google.gwt.view.client.HasData; import com.google.gwt.view.client.HasKeyProvider; @@ -83,16 +83,6 @@ } /** - * The loading state of the data. - */ - static enum LoadingState { - LOADING, // Waiting for data to load. - PARTIALLY_LOADED, // Partial page data loaded. - LOADED, // All page data loaded. - EMPTY; // The data size is 0. - } - - /** * The view that this presenter presents. * * @param <T> the data type @@ -474,6 +464,11 @@ return view.addHandler(handler, CellPreviewEvent.getType()); } + public HandlerRegistration addLoadingStateChangeHandler( + LoadingStateChangeEvent.Handler handler) { + return view.addHandler(handler, LoadingStateChangeEvent.TYPE); + } + public HandlerRegistration addRangeChangeHandler( RangeChangeEvent.Handler handler) { return view.addHandler(handler, RangeChangeEvent.getType()); @@ -657,6 +652,16 @@ return pendingState != null; } + /** + * Check whether or not the data set is empty. That is, the row count is + * exactly 0. + * + * @return true if data set is empty + */ + public boolean isEmpty() { + return isRowCountExact() && getRowCount() == 0; + } + public boolean isRowCountExact() { return getCurrentState().isRowCountExact(); } @@ -1512,9 +1517,7 @@ private void updateLoadingState() { int cacheSize = getVisibleItemCount(); int curPageSize = isRowCountExact() ? getCurrentPageSize() : getPageSize(); - if (getRowCount() == 0 && isRowCountExact()) { - view.setLoadingState(LoadingState.EMPTY); - } else if (cacheSize >= curPageSize) { + if (cacheSize >= curPageSize) { view.setLoadingState(LoadingState.LOADED); } else if (cacheSize == 0) { view.setLoadingState(LoadingState.LOADING);
diff --git a/user/src/com/google/gwt/user/cellview/client/LoadingStateChangeEvent.java b/user/src/com/google/gwt/user/cellview/client/LoadingStateChangeEvent.java new file mode 100644 index 0000000..16fc719 --- /dev/null +++ b/user/src/com/google/gwt/user/cellview/client/LoadingStateChangeEvent.java
@@ -0,0 +1,100 @@ +/* + * 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.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; + +/** + * An event used to indicate that the data loading state has changed. + */ +public class LoadingStateChangeEvent extends + GwtEvent<LoadingStateChangeEvent.Handler> { + + /** + * Implemented by handlers of {@link LoadingStateChangeEvent}. + */ + public interface Handler extends EventHandler { + /** + * Called when a {@link LoadingStateChangeEvent} is fired. + * + * @param event the {@link LoadingStateChangeEvent} + */ + void onLoadingStateChanged(LoadingStateChangeEvent event); + } + + /** + * Represents the current status of the data being loaded. + */ + public static interface LoadingState { + /** + * Indicates that the data has started to load. + */ + LoadingState LOADING = new DefaultLoadingState(); + + /** + * Indicates that part of the data set has been loaded, but more data is + * still pending. + */ + LoadingState PARTIALLY_LOADED = new DefaultLoadingState(); + + /** + * Indicates that the data set has been completely loaded. + */ + LoadingState LOADED = new DefaultLoadingState(); + } + + /** + * Default implementation of {@link LoadingState}. + */ + private static class DefaultLoadingState implements LoadingState { + } + + /** + * A singleton instance of Type. + */ + public static final Type<Handler> TYPE = new Type<Handler>(); + + private final LoadingState state; + + /** + * Construct a new {@link LoadingStateChangeEvent}. + * + * @param state the new state + */ + public LoadingStateChangeEvent(LoadingState state) { + this.state = state; + } + + @Override + public Type<Handler> getAssociatedType() { + return TYPE; + } + + /** + * Get the new {@link LoadingState} associated with this event. + * + * @return the {@link LoadingState} + */ + public LoadingState getLoadingState() { + return state; + } + + @Override + protected void dispatch(Handler handler) { + handler.onLoadingStateChanged(this); + } +}
diff --git a/user/test/com/google/gwt/user/cellview/client/CellListTest.java b/user/test/com/google/gwt/user/cellview/client/CellListTest.java index cc42e22..98b5b9e 100644 --- a/user/test/com/google/gwt/user/cellview/client/CellListTest.java +++ b/user/test/com/google/gwt/user/cellview/client/CellListTest.java
@@ -21,6 +21,8 @@ import com.google.gwt.dom.client.NativeEvent; 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.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.view.client.ProvidesKey; @@ -89,6 +91,39 @@ assertEquals(10, rendered.size()); } + @SuppressWarnings("deprecation") + public void testSetEmptyListWidget() { + CellList<String> cellList = createAbstractHasData(new TextCell()); + + // Set a widget. + Label l = new Label("Empty"); + cellList.setEmptyListWidget(l); + assertEquals(l, cellList.getEmptyListWidget()); + + // Set a message. + SafeHtml message = SafeHtmlUtils.fromString("empty"); + cellList.setEmptyListMessage(message); + assertEquals(message, cellList.getEmptyListMessage()); + assertNotSame(l, cellList.getEmptyListWidget()); + + // Null widget. + cellList.setEmptyListWidget(null); + assertNull(cellList.getEmptyListWidget()); + } + + public void testSetLoadingIndicator() { + CellList<String> cellList = createAbstractHasData(new TextCell()); + + // Set a widget. + Label l = new Label("Loading"); + cellList.setLoadingIndicator(l); + assertEquals(l, cellList.getLoadingIndicator()); + + // Null widget. + cellList.setLoadingIndicator(null); + assertNull(cellList.getLoadingIndicator()); + } + @Override protected CellList<String> createAbstractHasData(Cell<String> cell) { return new CellList<String>(cell);
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java index 9147ffd..6c27ac2 100644 --- a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java +++ b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
@@ -33,6 +33,7 @@ import com.google.gwt.user.cellview.client.CellTable.Style; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import java.util.ArrayList; @@ -424,6 +425,32 @@ assertEquals("", col1.getStyle().getWidth()); } + public void testSetEmptyListWidget() { + CellTable<String> table = createAbstractHasData(new TextCell()); + + // Set a widget. + Label l = new Label("Empty"); + table.setEmptyTableWidget(l); + assertEquals(l, table.getEmptyTableWidget()); + + // Null widget. + table.setEmptyTableWidget(null); + assertNull(table.getEmptyTableWidget()); + } + + public void testSetLoadingIndicator() { + CellTable<String> table = createAbstractHasData(new TextCell()); + + // Set a widget. + Label l = new Label("Loading"); + table.setLoadingIndicator(l); + assertEquals(l, table.getLoadingIndicator()); + + // Null widget. + table.setLoadingIndicator(null); + assertNull(table.getLoadingIndicator()); + } + public void testSetTableLayoutFixed() { CellTable<String> table = createAbstractHasData(new TextCell()); assertNotSame("fixed",
diff --git a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java index 1ff0fa6..358a5c9 100644 --- a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java +++ b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
@@ -23,10 +23,10 @@ import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState; import com.google.gwt.user.cellview.client.HasDataPresenter.View; import com.google.gwt.user.cellview.client.HasKeyboardPagingPolicy.KeyboardPagingPolicy; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; +import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState; import com.google.gwt.view.client.HasData; import com.google.gwt.view.client.MockHasData; import com.google.gwt.view.client.MockHasData.MockRangeChangeHandler; @@ -497,6 +497,31 @@ assertEquals(5, presenter.getCurrentPageSize()); } + public void testIsEmpty() { + HasData<String> listView = new MockHasData<String>(); + MockView<String> view = new MockView<String>(); + HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView, + view, 10, null); + + // Non-zero row count. + presenter.setRowCount(1, true); + populatePresenter(presenter); + presenter.flush(); + assertFalse(presenter.isEmpty()); + + // Zero row count with unknown size. + presenter.setRowCount(0, false); + populatePresenter(presenter); + presenter.flush(); + assertFalse(presenter.isEmpty()); + + // Zero row count with known size. + presenter.setRowCount(0, true); + populatePresenter(presenter); + presenter.flush(); + assertFalse(presenter.isEmpty()); + } + public void testKeyboardNavigationChangePage() { HasData<String> listView = new MockHasData<String>(); MockView<String> view = new MockView<String>(); @@ -1246,7 +1271,7 @@ assertEquals(0, presenter.getRowCount()); assertTrue(presenter.isRowCountExact()); presenter.flush(); - view.assertLoadingState(LoadingState.EMPTY); + view.assertLoadingState(LoadingState.LOADED); } public void testSetRowCountNoBoolean() {