Adding SuggestionDisplay interface to SuggestBox to allow custom display implementations. Patch by: jlabanca Review by: jgw git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7400 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/SuggestBox.java b/user/src/com/google/gwt/user/client/ui/SuggestBox.java index b18ba72..060cf8e 100644 --- a/user/src/com/google/gwt/user/client/ui/SuggestBox.java +++ b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
@@ -92,48 +92,6 @@ * <dl> * <dt>.gwt-SuggestBox</dt> * <dd>the suggest box itself</dd> - * <dt>.gwt-SuggestBoxPopup</dt> - * <dd>the suggestion popup</dd> - * <dt>.gwt-SuggestBoxPopup .item</dt> - * <dd>an unselected suggestion</dd> - * <dt>.gwt-SuggestBoxPopup .item-selected</dt> - * <dd>a selected suggestion</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopLeft</dt> - * <dd>the top left cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopLeftInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopCenter</dt> - * <dd>the top center cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopCenterInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopRight</dt> - * <dd>the top right cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupTopRightInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleLeft</dt> - * <dd>the middle left cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleLeftInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleCenter</dt> - * <dd>the middle center cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleCenterInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleRight</dt> - * <dd>the middle right cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleRightInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomLeft</dt> - * <dd>the bottom left cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomLeftInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomCenter</dt> - * <dd>the bottom center cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomCenterInner</dt> - * <dd>the inner element of the cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomRight</dt> - * <dd>the bottom right cell</dd> - * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomRightInner</dt> - * <dd>the inner element of the cell</dd> * </dl> * * @see SuggestOracle @@ -147,6 +105,364 @@ HasValue<String>, HasSelectionHandlers<Suggestion> { /** + * The callback used when a user selects a {@link Suggestion}. + */ + public static interface SuggestionCallback { + void onSuggestionSelected(Suggestion suggestion); + } + + /** + * Used to display suggestions to the user. + */ + public abstract static class SuggestionDisplay { + + /** + * Get the currently selected {@link Suggestion} in the display. + * + * @return the current suggestion, or null if none selected + */ + protected abstract Suggestion getCurrentSelection(); + + /** + * Hide the list of suggestions from view. + */ + protected abstract void hideSuggestions(); + + /** + * Highlight the suggestion directly below the current selection in the + * list. + */ + protected abstract void moveSelectionDown(); + + /** + * Highlight the suggestion directly above the current selection in the + * list. + */ + protected abstract void moveSelectionUp(); + + /** + * Set the debug id of widgets used in the SuggestionDisplay. + * + * @param suggestBoxBaseID the baseID of the {@link SuggestBox} + * @see UIObject#onEnsureDebugId(String) + */ + protected void onEnsureDebugId(String suggestBoxBaseID) { + } + + /** + * Update the list of visible suggestions. + * + * @param suggestBox the suggest box where the suggestions originated + * @param suggestions the suggestions to show + * @param isDisplayStringHTML should the suggestions be displayed as HTML + * @param isAutoSelectEnabled if true, the first item should be selected + * automatically + * @param callback the callback used when the user makes a suggestion + */ + protected abstract void showSuggestions(SuggestBox suggestBox, + Collection<? extends Suggestion> suggestions, + boolean isDisplayStringHTML, boolean isAutoSelectEnabled, + SuggestionCallback callback); + + /** + * This is here for legacy reasons. It is intentionally not visible. + * + * @deprecated implemented in DefaultSuggestionDisplay + */ + @Deprecated + boolean isAnimationEnabledImpl() { + // Implemented in DefaultSuggestionDisplay. + return false; + } + + /** + * This is here for legacy reasons. It is intentionally not visible. + * + * @deprecated implemented in DefaultSuggestionDisplay + */ + @Deprecated + boolean isSuggestionListShowingImpl() { + // Implemented in DefaultSuggestionDisplay. + return false; + } + + /** + * This is here for legacy reasons. It is intentionally not visible. + * + * @deprecated implemented in DefaultSuggestionDisplay + */ + @Deprecated + void setAnimationEnabledImpl(boolean enable) { + // Implemented in DefaultSuggestionDisplay. + } + + /** + * This is here for legacy reasons. It is intentionally not visible. + * + * @deprecated implemented in DefaultSuggestionDisplay + */ + @Deprecated + void setPopupStyleNameImpl(String style) { + // Implemented in DefaultSuggestionDisplay. + } + } + + /** + * <p> + * The default implementation of {@link SuggestionDisplay} displays + * suggestions in a {@link PopupPanel} beneath the {@link SuggestBox}. + * </p> + * + * <h3>CSS Style Rules</h3> + * <dl> + * <dt>.gwt-SuggestBoxPopup</dt> + * <dd>the suggestion popup</dd> + * <dt>.gwt-SuggestBoxPopup .item</dt> + * <dd>an unselected suggestion</dd> + * <dt>.gwt-SuggestBoxPopup .item-selected</dt> + * <dd>a selected suggestion</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopLeft</dt> + * <dd>the top left cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopLeftInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopCenter</dt> + * <dd>the top center cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopCenterInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopRight</dt> + * <dd>the top right cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupTopRightInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleLeft</dt> + * <dd>the middle left cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleLeftInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleCenter</dt> + * <dd>the middle center cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleCenterInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleRight</dt> + * <dd>the middle right cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupMiddleRightInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomLeft</dt> + * <dd>the bottom left cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomLeftInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomCenter</dt> + * <dd>the bottom center cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomCenterInner</dt> + * <dd>the inner element of the cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomRight</dt> + * <dd>the bottom right cell</dd> + * <dt>.gwt-SuggestBoxPopup .suggestPopupBottomRightInner</dt> + * <dd>the inner element of the cell</dd> + * </dl> + */ + public static class DefaultSuggestionDisplay extends SuggestionDisplay + implements HasAnimation { + + private final SuggestionMenu suggestionMenu; + private final PopupPanel suggestionPopup; + + /** + * We need to keep track of the last {@link SuggestBox} because it acts as + * an autoHide partner for the {@link PopupPanel}. If we use the same + * display for multiple {@link SuggestBox}, we need to switch the autoHide + * partner. + */ + private SuggestBox lastSuggestBox = null; + + /** + * Construct a new {@link DefaultSuggestionDisplay}. + */ + public DefaultSuggestionDisplay() { + suggestionMenu = new SuggestionMenu(true); + suggestionPopup = createPopup(); + suggestionPopup.setWidget(decorateSuggestionList(suggestionMenu)); + } + + @Override + public void hideSuggestions() { + suggestionPopup.hide(); + } + + public boolean isAnimationEnabled() { + return suggestionPopup.isAnimationEnabled(); + } + + /** + * Check whether or not the list of suggestions is being shown. + * + * @return true if the suggestions are visible, false if not + */ + public boolean isSuggestionListShowing() { + return suggestionPopup.isShowing(); + } + + public void setAnimationEnabled(boolean enable) { + suggestionPopup.setAnimationEnabled(enable); + } + + /** + * Sets the style name of the suggestion popup. + * + * @param style the new primary style name + * @see UIObject#setStyleName(String) + */ + public void setPopupStyleName(String style) { + suggestionPopup.setStyleName(style); + } + + /** + * Create the PopupPanel that will hold the list of suggestions. + * + * @return the popup panel + */ + protected PopupPanel createPopup() { + PopupPanel p = new DecoratedPopupPanel(true, false, "suggestPopup"); + p.setStyleName("gwt-SuggestBoxPopup"); + p.setPreviewingAllNativeEvents(true); + p.setAnimationType(AnimationType.ROLL_DOWN); + return p; + } + + /** + * Wrap the list of suggestions before adding it to the popup. You can + * override this method if you want to wrap the suggestion list in a + * decorator. + * + * @param suggestionList the widget that contains the list of suggestions + * @return the suggestList, optionally inside of a wrapper + */ + protected Widget decorateSuggestionList(Widget suggestionList) { + return suggestionList; + } + + @Override + protected Suggestion getCurrentSelection() { + if (!isSuggestionListShowing()) { + return null; + } + MenuItem item = suggestionMenu.getSelectedItem(); + return item == null ? null : ((SuggestionMenuItem) item).getSuggestion(); + } + + /** + * Get the {@link PopupPanel} used to display suggestions. + * + * @return the popup panel + */ + protected PopupPanel getPopupPanel() { + return suggestionPopup; + } + + @Override + protected void moveSelectionDown() { + // Make sure that the menu is actually showing. These keystrokes + // are only relevant when choosing a suggestion. + if (isSuggestionListShowing()) { + suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() + 1); + } + } + + @Override + protected void moveSelectionUp() { + // Make sure that the menu is actually showing. These keystrokes + // are only relevant when choosing a suggestion. + if (isSuggestionListShowing()) { + suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() - 1); + } + } + + /** + * <b>Affected Elements:</b> + * <ul> + * <li>-popup = The popup that appears with suggestions.</li> + * <li>-item# = The suggested item at the specified index.</li> + * </ul> + * + * @see UIObject#onEnsureDebugId(String) + */ + @Override + protected void onEnsureDebugId(String baseID) { + suggestionPopup.ensureDebugId(baseID + "-popup"); + suggestionMenu.setMenuItemDebugIds(baseID); + } + + @Override + protected void showSuggestions(final SuggestBox suggestBox, + Collection<? extends Suggestion> suggestions, + boolean isDisplayStringHTML, boolean isAutoSelectEnabled, + final SuggestionCallback callback) { + // Hide the popup if there are no suggestions to display. + if (suggestions == null || suggestions.size() == 0) { + hideSuggestions(); + return; + } + + // Hide the popup before we manipulate the menu within it. If we do not + // do this, some browsers will redraw the popup as items are removed + // and added to the menu. + if (suggestionPopup.isAttached()) { + suggestionPopup.hide(); + } + + suggestionMenu.clearItems(); + + for (final Suggestion curSuggestion : suggestions) { + final SuggestionMenuItem menuItem = new SuggestionMenuItem( + curSuggestion, isDisplayStringHTML); + menuItem.setCommand(new Command() { + public void execute() { + callback.onSuggestionSelected(curSuggestion); + } + }); + + suggestionMenu.addItem(menuItem); + } + + if (isAutoSelectEnabled) { + // Select the first item in the suggestion menu. + suggestionMenu.selectItem(0); + } + + // Link the popup autoHide to the TextBox. + if (lastSuggestBox != suggestBox) { + // If the suggest box has changed, free the old one first. + if (lastSuggestBox != null) { + suggestionPopup.removeAutoHidePartner(lastSuggestBox.getElement()); + } + lastSuggestBox = suggestBox; + suggestionPopup.addAutoHidePartner(suggestBox.getElement()); + } + + // Show the popup under the TextBox. + suggestionPopup.showRelativeTo(suggestBox); + } + + @Override + boolean isAnimationEnabledImpl() { + return isAnimationEnabled(); + } + + @Override + boolean isSuggestionListShowingImpl() { + return isSuggestionListShowing(); + } + + @Override + void setAnimationEnabledImpl(boolean enable) { + setAnimationEnabled(enable); + } + + @Override + void setPopupStyleNameImpl(String style) { + setPopupStyleName(style); + } + } + + /** * The SuggestionMenu class is used for the display and selection of * suggestions in the SuggestBox widget. SuggestionMenu differs from MenuBar * in that it always has a vertical orientation, and it has no submenus. It @@ -273,12 +589,18 @@ private boolean selectsFirstItem = true; private SuggestOracle oracle; private String currentText; - private final SuggestionMenu suggestionMenu; - private final PopupPanel suggestionPopup; + private final SuggestionDisplay display; private final TextBoxBase box; private final Callback callback = new Callback() { public void onSuggestionsReady(Request request, Response response) { - showSuggestions(response.getSuggestions()); + display.showSuggestions(SuggestBox.this, response.getSuggestions(), + oracle.isDisplayStringHTML(), isAutoSelectEnabled(), + suggestionCallback); + } + }; + private final SuggestionCallback suggestionCallback = new SuggestionCallback() { + public void onSuggestionSelected(Suggestion suggestion) { + setNewSelection(suggestion); } }; @@ -310,14 +632,23 @@ * @param box the text widget */ public SuggestBox(SuggestOracle oracle, TextBoxBase box) { - this.box = box; - initWidget(box); + this(oracle, box, new DefaultSuggestionDisplay()); + } - // suggestionMenu must be created before suggestionPopup, because - // suggestionMenu is suggestionPopup's widget - suggestionMenu = new SuggestionMenu(true); - suggestionPopup = createPopup(); - suggestionPopup.setAnimationType(AnimationType.ROLL_DOWN); + /** + * Constructor for {@link SuggestBox}. The text box will be removed from it's + * current location and wrapped by the {@link SuggestBox}. + * + * @param oracle supplies suggestions based upon the current contents of the + * text widget + * @param box the text widget + * @param suggestDisplay the class used to display suggestions + */ + public SuggestBox(SuggestOracle oracle, TextBoxBase box, + SuggestionDisplay suggestDisplay) { + this.box = box; + this.display = suggestDisplay; + initWidget(box); addEventsToTextBox(); @@ -335,7 +666,8 @@ */ @Deprecated public void addChangeListener(final ChangeListener listener) { - ListenerWrapper.WrappedLogicalChangeListener.add(box, listener).setSource(this); + ListenerWrapper.WrappedLogicalChangeListener.add(box, listener).setSource( + this); } /** @@ -347,8 +679,8 @@ */ @Deprecated public void addClickListener(final ClickListener listener) { - ListenerWrapper.WrappedClickListener legacy = ListenerWrapper.WrappedClickListener.add(box, - listener); + ListenerWrapper.WrappedClickListener legacy = ListenerWrapper.WrappedClickListener.add( + box, listener); legacy.setSource(this); } @@ -367,18 +699,19 @@ * source Widget for these events will be the SuggestBox. * * @param listener the listener interface to add - * @deprecated use {@link #getTextBox}().addFocusHandler/addBlurHandler() instead + * @deprecated use {@link #getTextBox}().addFocusHandler/addBlurHandler() + * instead */ @Deprecated public void addFocusListener(final FocusListener listener) { - ListenerWrapper.WrappedFocusListener focus = ListenerWrapper.WrappedFocusListener.add(box, - listener); + ListenerWrapper.WrappedFocusListener focus = ListenerWrapper.WrappedFocusListener.add( + box, listener); focus.setSource(this); } /** - * @deprecated Use {@link #addKeyDownHandler}, {@link - * #addKeyUpHandler} and {@link #addKeyPressHandler} instead + * @deprecated Use {@link #addKeyDownHandler}, {@link #addKeyUpHandler} and + * {@link #addKeyPressHandler} instead */ @Deprecated public void addKeyboardListener(KeyboardListener listener) { @@ -419,6 +752,15 @@ } /** + * Get the {@link SuggestionDisplay} used to display suggestions. + * + * @return the {@link SuggestionDisplay} + */ + public SuggestionDisplay getSuggestionDisplay() { + return display; + } + + /** * Gets the suggest box's {@link com.google.gwt.user.client.ui.SuggestOracle}. * * @return the {@link SuggestOracle} @@ -449,14 +791,27 @@ } /** - * Hide current suggestions. + * Hide current suggestions in the {@link DefaultSuggestionDisplay}. Note that + * this method is a no-op unless the {@link DefaultSuggestionDisplay} is used. + * + * @deprecated use {@link DefaultSuggestionDisplay#hideSuggestions()} instead */ + @Deprecated public void hideSuggestionList() { - this.suggestionPopup.hide(); + display.hideSuggestions(); } + /** + * Check whether or not the {@link DefaultSuggestionDisplay} has animations + * enabled. Note that this method only has a meaningful return value when the + * {@link DefaultSuggestionDisplay} is used. + * + * @deprecated use {@link DefaultSuggestionDisplay#isAnimationEnabled()} + * instead + */ + @Deprecated public boolean isAnimationEnabled() { - return suggestionPopup.isAnimationEnabled(); + return display.isAnimationEnabledImpl(); } /** @@ -470,15 +825,22 @@ } /** + * Check if the {@link DefaultSuggestionDisplay} is showing. Note that this + * method only has a meaningful return value when the + * {@link DefaultSuggestionDisplay} is used. + * * @return true if the list of suggestions is currently showing, false if not + * @deprecated use {@link DefaultSuggestionDisplay#isSuggestionListShowing()} */ + @Deprecated public boolean isSuggestionListShowing() { - return suggestionPopup.isShowing(); + return display.isSuggestionListShowingImpl(); } /** - * @deprecated Use the {@link HandlerRegistration#removeHandler} - * method on the object returned by {@link #getTextBox}().addChangeHandler instead + * @deprecated Use the {@link HandlerRegistration#removeHandler} method on the + * object returned by {@link #getTextBox}().addChangeHandler + * instead */ @Deprecated public void removeChangeListener(ChangeListener listener) { @@ -486,8 +848,9 @@ } /** - * @deprecated Use the {@link HandlerRegistration#removeHandler} - * method on the object returned by {@link #getTextBox}().addClickHandler instead + * @deprecated Use the {@link HandlerRegistration#removeHandler} method on the + * object returned by {@link #getTextBox}().addClickHandler + * instead */ @Deprecated public void removeClickListener(ClickListener listener) { @@ -495,8 +858,8 @@ } /** - * @deprecated Use the {@link HandlerRegistration#removeHandler} - * method no the object returned by {@link #addSelectionHandler} instead + * @deprecated Use the {@link HandlerRegistration#removeHandler} method no the + * object returned by {@link #addSelectionHandler} instead */ @Deprecated public void removeEventHandler(SuggestionHandler handler) { @@ -504,8 +867,9 @@ } /** - * @deprecated Use the {@link HandlerRegistration#removeHandler} - * method on the object returned by {@link #getTextBox}().addFocusListener instead + * @deprecated Use the {@link HandlerRegistration#removeHandler} method on the + * object returned by {@link #getTextBox}().addFocusListener + * instead */ @Deprecated public void removeFocusListener(FocusListener listener) { @@ -513,8 +877,8 @@ } /** - * @deprecated Use the {@link HandlerRegistration#removeHandler} - * method on the object returned by {@link #getTextBox}().add*Handler instead + * @deprecated Use the {@link HandlerRegistration#removeHandler} method on the + * object returned by {@link #getTextBox}().add*Handler instead */ @Deprecated public void removeKeyboardListener(KeyboardListener listener) { @@ -525,8 +889,18 @@ box.setAccessKey(key); } + /** + * Enable or disable animations in the {@link DefaultSuggestionDisplay}. Note + * that this method is a no-op unless the {@link DefaultSuggestionDisplay} is + * used. + * + * @deprecated use + * {@link DefaultSuggestionDisplay#setAnimationEnabled(boolean)} + * instead + */ + @Deprecated public void setAnimationEnabled(boolean enable) { - suggestionPopup.setAnimationEnabled(enable); + display.setAnimationEnabledImpl(enable); } /** @@ -555,13 +929,18 @@ } /** - * Sets the style name of the suggestion popup. + * Sets the style name of the suggestion popup in the + * {@link DefaultSuggestionDisplay}. Note that this method is a no-op unless + * the {@link DefaultSuggestionDisplay} is used. * * @param style the new primary style name * @see UIObject#setStyleName(String) + * @deprecated use {@link DefaultSuggestionDisplay#setPopupStyleName(String)} + * instead */ + @Deprecated public void setPopupStyleName(String style) { - suggestionPopup.setStyleName(style); + getSuggestionDisplay().setPopupStyleNameImpl(style); } public void setTabIndex(int index) { @@ -590,47 +969,10 @@ } } - /** - * <b>Affected Elements:</b> - * <ul> - * <li>-popup = The popup that appears with suggestions.</li> - * <li>-items-item# = The suggested item at the specified index.</li> - * </ul> - * - * @see UIObject#onEnsureDebugId(String) - */ @Override protected void onEnsureDebugId(String baseID) { super.onEnsureDebugId(baseID); - suggestionPopup.ensureDebugId(baseID + "-popup"); - suggestionMenu.setMenuItemDebugIds(baseID); - } - - /** - * Gets the specified suggestion from the suggestions currently showing. - * - * @param index the index at which the suggestion lives - * - * @throws IndexOutOfBoundsException if the index is greater then the number - * of suggestions currently showing - * - * @return the given suggestion - */ - Suggestion getSuggestion(int index) { - if (!isSuggestionListShowing()) { - throw new IndexOutOfBoundsException( - "No suggestions showing, so cannot show " + index); - } - return ((SuggestionMenuItem) suggestionMenu.getItems().get(index)).suggestion; - } - - /** - * Get the number of suggestions that are currently showing. - * - * @return the number of suggestions currently showing, 0 if there are none - */ - int getSuggestionCount() { - return isSuggestionListShowing() ? suggestionMenu.getNumItems() : 0; + display.onEnsureDebugId(baseID); } void showSuggestions(String query) { @@ -646,25 +988,22 @@ ValueChangeHandler<String> { public void onKeyDown(KeyDownEvent event) { - // Make sure that the menu is actually showing. These keystrokes - // are only relevant when choosing a suggestion. - if (suggestionPopup.isAttached()) { - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_DOWN: - suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() + 1); - break; - case KeyCodes.KEY_UP: - suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() - 1); - break; - case KeyCodes.KEY_ENTER: - case KeyCodes.KEY_TAB: - if (suggestionMenu.getSelectedItemIndex() < 0) { - suggestionPopup.hide(); - } else { - suggestionMenu.doSelectedItemAction(); - } - break; - } + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_DOWN: + display.moveSelectionDown(); + break; + case KeyCodes.KEY_UP: + display.moveSelectionUp(); + break; + case KeyCodes.KEY_ENTER: + case KeyCodes.KEY_TAB: + Suggestion suggestion = display.getCurrentSelection(); + if (suggestion == null) { + display.hideSuggestions(); + } else { + setNewSelection(suggestion); + } + break; } delegateEvent(SuggestBox.this, event); } @@ -689,15 +1028,6 @@ box.addValueChangeHandler(events); } - private PopupPanel createPopup() { - PopupPanel p = new DecoratedPopupPanel(true, false, "suggestPopup"); - p.setWidget(suggestionMenu); - p.setStyleName("gwt-SuggestBoxPopup"); - p.setPreviewingAllNativeEvents(true); - p.addAutoHidePartner(getTextBox().getElement()); - return p; - } - private void fireSuggestionEvent(Suggestion selectedSuggestion) { SelectionEvent.fire(this, selectedSuggestion); } @@ -713,11 +1043,16 @@ showSuggestions(text); } - private void setNewSelection(SuggestionMenuItem menuItem) { - Suggestion curSuggestion = menuItem.getSuggestion(); + /** + * Set the new suggestion in the text box. + * + * @param curSuggestion the new suggestion + */ + private void setNewSelection(Suggestion curSuggestion) { + assert curSuggestion != null : "suggestion cannot be null"; currentText = curSuggestion.getReplacementString(); setText(currentText); - suggestionPopup.hide(); + display.hideSuggestions(); fireSuggestionEvent(curSuggestion); } @@ -729,46 +1064,4 @@ private void setOracle(SuggestOracle oracle) { this.oracle = oracle; } - - /** - * Show the given collection of suggestions. - * - * @param suggestions suggestions to show - */ - private void showSuggestions(Collection<? extends Suggestion> suggestions) { - if (suggestions.size() > 0) { - - // Hide the popup before we manipulate the menu within it. If we do not - // do this, some browsers will redraw the popup as items are removed - // and added to the menu. - boolean isAnimationEnabled = suggestionPopup.isAnimationEnabled(); - if (suggestionPopup.isAttached()) { - suggestionPopup.hide(); - } - - suggestionMenu.clearItems(); - - for (Suggestion curSuggestion : suggestions) { - final SuggestionMenuItem menuItem = new SuggestionMenuItem( - curSuggestion, oracle.isDisplayStringHTML()); - menuItem.setCommand(new Command() { - public void execute() { - SuggestBox.this.setNewSelection(menuItem); - } - }); - - suggestionMenu.addItem(menuItem); - } - - if (selectsFirstItem) { - // Select the first item in the suggestion menu. - suggestionMenu.selectItem(0); - } - - suggestionPopup.showRelativeTo(getTextBox()); - suggestionPopup.setAnimationEnabled(isAnimationEnabled); - } else { - suggestionPopup.hide(); - } - } }
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java index 079dab7..507a5a4 100644 --- a/user/test/com/google/gwt/user/UISuite.java +++ b/user/test/com/google/gwt/user/UISuite.java
@@ -39,6 +39,7 @@ import com.google.gwt.user.client.ui.DecoratedTabBarTest; import com.google.gwt.user.client.ui.DecoratedTabPanelTest; import com.google.gwt.user.client.ui.DecoratorPanelTest; +import com.google.gwt.user.client.ui.DefaultSuggestionDisplayTest; import com.google.gwt.user.client.ui.DelegatingKeyboardListenerCollectionTest; import com.google.gwt.user.client.ui.DialogBoxTest; import com.google.gwt.user.client.ui.DisclosurePanelTest; @@ -122,6 +123,7 @@ suite.addTestSuite(DecoratedTabBarTest.class); suite.addTestSuite(DecoratedTabPanelTest.class); suite.addTestSuite(DecoratorPanelTest.class); + suite.addTestSuite(DefaultSuggestionDisplayTest.class); suite.addTestSuite(DelegatingKeyboardListenerCollectionTest.class); suite.addTestSuite(DialogBoxTest.class); suite.addTestSuite(DisclosurePanelTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/DefaultSuggestionDisplayTest.java b/user/test/com/google/gwt/user/client/ui/DefaultSuggestionDisplayTest.java new file mode 100644 index 0000000..44b8c7d --- /dev/null +++ b/user/test/com/google/gwt/user/client/ui/DefaultSuggestionDisplayTest.java
@@ -0,0 +1,85 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.user.client.ui; + +import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; + +import java.util.List; + +/** + * Tests for {@link DefaultSuggestionDisplay}. + */ +public class DefaultSuggestionDisplayTest extends SuggestionDisplayTestBase { + + public void testAccessors() { + SuggestBox box = createSuggestBox(); + DefaultSuggestionDisplay display = (DefaultSuggestionDisplay) box.getSuggestionDisplay(); + PopupPanel popup = display.getPopupPanel(); + + // isAnimationEnabled. + assertFalse(display.isAnimationEnabled()); + assertFalse(popup.isAnimationEnabled()); + display.setAnimationEnabled(true); + assertTrue(display.isAnimationEnabled()); + assertTrue(popup.isAnimationEnabled()); + + // isSuggestListShowing. + List<Suggestion> suggestions = createSuggestions("test0", "test1", "test2"); + assertFalse(display.isSuggestionListShowing()); + assertFalse(popup.isShowing()); + display.showSuggestions(box, suggestions, false, false, NULL_CALLBACK); + assertTrue(display.isSuggestionListShowing()); + assertTrue(popup.isShowing()); + display.hideSuggestions(); + assertFalse(display.isSuggestionListShowing()); + assertFalse(popup.isShowing()); + } + + public void testGetCurrentSelectionWhenHidden() { + SuggestBox box = createSuggestBox(); + DefaultSuggestionDisplay display = (DefaultSuggestionDisplay) box.getSuggestionDisplay(); + + // Show the suggestions and select the first item. + List<Suggestion> suggestions = createSuggestions("test0", "test1", "test2"); + display.showSuggestions(box, suggestions, false, true, NULL_CALLBACK); + assertTrue(display.isSuggestionListShowing()); + assertEquals(suggestions.get(0), display.getCurrentSelection()); + + // Hide the list and ensure that nothing is selected. + display.hideSuggestions(); + assertNull(display.getCurrentSelection()); + } + + public void testShowSuggestionsEmpty() { + SuggestBox box = createSuggestBox(); + DefaultSuggestionDisplay display = (DefaultSuggestionDisplay) box.getSuggestionDisplay(); + + // Show null suggestions. + display.showSuggestions(box, null, false, true, NULL_CALLBACK); + assertFalse(display.isSuggestionListShowing()); + + // Show empty suggestions. + List<Suggestion> suggestions = createSuggestions(); + display.showSuggestions(box, suggestions, false, true, NULL_CALLBACK); + assertFalse(display.isSuggestionListShowing()); + } + + @Override + protected DefaultSuggestionDisplay createSuggestionDisplay() { + return new DefaultSuggestionDisplay(); + } +}
diff --git a/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java b/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java index feb84c1..8bf2db5 100644 --- a/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java +++ b/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
@@ -17,14 +17,57 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; -import com.google.gwt.junit.client.GWTTestCase; +import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay; +import com.google.gwt.user.client.ui.SuggestBox.SuggestionCallback; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; /** * Tests for {@link SuggestBoxTest}. */ -public class SuggestBoxTest extends GWTTestCase { +public class SuggestBoxTest extends WidgetTestBase { + + /** + * A SuggestionDisplay used for testing. + */ + private static class TestSuggestionDisplay extends DefaultSuggestionDisplay { + + private List<? extends Suggestion> suggestions; + + @Override + protected void showSuggestions(SuggestBox suggestBox, + Collection<? extends Suggestion> suggestions, + boolean isDisplayStringHTML, boolean isAutoSelectEnabled, + SuggestionCallback callback) { + super.showSuggestions(suggestBox, suggestions, isDisplayStringHTML, + isAutoSelectEnabled, callback); + this.suggestions = new ArrayList<Suggestion>(suggestions); + } + + /** + * Get the suggestion at the specified index. + * + * @param index the index + * @return the {@link Suggestion} at the index + */ + public Suggestion getSuggestion(int index) { + return suggestions.get(index); + } + + /** + * Get the number of suggestions that are currently showing. Used for + * testing. + * + * @return the number of suggestions currently showing, 0 if there are none + */ + public int getSuggestionCount() { + return suggestions.size(); + } + } @Override public String getModuleName() { @@ -34,6 +77,7 @@ /** * Test the basic accessors. */ + @SuppressWarnings("deprecation") public void testAccessors() { SuggestBox box = createSuggestBox(); @@ -53,44 +97,48 @@ assertTrue(box.isSuggestionListShowing()); } + @SuppressWarnings("deprecation") public void testShowAndHide() { SuggestBox box = createSuggestBox(); - assertFalse(box.isSuggestionListShowing()); + TestSuggestionDisplay display = (TestSuggestionDisplay) box.getSuggestionDisplay(); + assertFalse(display.isSuggestionListShowing()); + // should do nothing, box is not attached. box.showSuggestionList(); - assertFalse(box.isSuggestionListShowing()); + assertFalse(display.isSuggestionListShowing()); // Adds the suggest box to the root panel. RootPanel.get().add(box); - assertFalse(box.isSuggestionListShowing()); + assertFalse(display.isSuggestionListShowing()); // Hides the list of suggestions, should be a no-op. box.hideSuggestionList(); // Should try to show, but still fail, as there are no default suggestions. box.showSuggestionList(); - assertFalse(box.isSuggestionListShowing()); + assertFalse(display.isSuggestionListShowing()); // Now, finally, should be true box.setText("t"); box.showSuggestionList(); - assertTrue(box.isSuggestionListShowing()); + assertTrue(display.isSuggestionListShowing()); // Hides it for real this time. box.hideSuggestionList(); - assertFalse(box.isSuggestionListShowing()); + assertFalse(display.isSuggestionListShowing()); } public void testDefaults() { MultiWordSuggestOracle oracle = new MultiWordSuggestOracle(); oracle.setDefaultSuggestionsFromText(Arrays.asList("A", "B")); - SuggestBox box = new SuggestBox(oracle); + TestSuggestionDisplay display = new TestSuggestionDisplay(); + SuggestBox box = new SuggestBox(oracle, new TextBox(), display); RootPanel.get().add(box); box.showSuggestionList(); - assertTrue(box.isSuggestionListShowing()); - assertEquals(2, box.getSuggestionCount()); - assertEquals("A", box.getSuggestion(0).getReplacementString()); - assertEquals("B", box.getSuggestion(1).getReplacementString()); + assertTrue(display.isSuggestionListShowing()); + assertEquals(2, display.getSuggestionCount()); + assertEquals("A", display.getSuggestion(0).getReplacementString()); + assertEquals("B", display.getSuggestion(1).getReplacementString()); } public void testShowFirst() { @@ -106,12 +154,6 @@ // text box and ensure that we see the correct behavior. } - @Override - public void gwtTearDown() throws Exception { - super.gwtTearDown(); - RootPanel.get().clear(); - } - public void testWrapUsingStaticWrapMethod() { Element wrapper = Document.get().createTextInputElement(); RootPanel.get().getElement().appendChild(wrapper); @@ -133,7 +175,7 @@ protected SuggestBox createSuggestBox() { MultiWordSuggestOracle oracle = createOracle(); - return new SuggestBox(oracle); + return new SuggestBox(oracle, new TextBox(), new TestSuggestionDisplay()); } private MultiWordSuggestOracle createOracle() {
diff --git a/user/test/com/google/gwt/user/client/ui/SuggestionDisplayTestBase.java b/user/test/com/google/gwt/user/client/ui/SuggestionDisplayTestBase.java new file mode 100644 index 0000000..ad9188b --- /dev/null +++ b/user/test/com/google/gwt/user/client/ui/SuggestionDisplayTestBase.java
@@ -0,0 +1,149 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.user.client.ui; + +import com.google.gwt.user.client.ui.SuggestBox.SuggestionCallback; +import com.google.gwt.user.client.ui.SuggestBox.SuggestionDisplay; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base tests for {@link SuggestionDisplay}. + */ +public abstract class SuggestionDisplayTestBase extends WidgetTestBase { + + /** + * A no-op callback used for testing. + */ + protected static final SuggestionCallback NULL_CALLBACK = new SuggestionCallback() { + public void onSuggestionSelected(Suggestion suggestion) { + } + }; + + /** + * A simple {@link Suggestion} implementation that uses a single string for + * both the display and replacement string. + */ + private static class SimpleSuggestion implements Suggestion { + + public String text; + + public SimpleSuggestion(String text) { + this.text = text; + } + + public String getDisplayString() { + return text; + } + + public String getReplacementString() { + return text; + } + } + + public void testMoveSelectionUpAndDown() { + SuggestBox box = new SuggestBox(); + SuggestionDisplay display = box.getSuggestionDisplay(); + SuggestOracle oracle = box.getSuggestOracle(); + + // Show some suggestions. + List<Suggestion> suggestions = createSuggestions("test0", "test1", "test2", + "test3"); + display.showSuggestions(box, suggestions, false, false, NULL_CALLBACK); + assertNull(display.getCurrentSelection()); + + display.moveSelectionDown(); + assertEquals(suggestions.get(0), display.getCurrentSelection()); + display.moveSelectionDown(); + assertEquals(suggestions.get(1), display.getCurrentSelection()); + display.moveSelectionDown(); + assertEquals(suggestions.get(2), display.getCurrentSelection()); + display.moveSelectionUp(); + assertEquals(suggestions.get(1), display.getCurrentSelection()); + display.moveSelectionUp(); + assertEquals(suggestions.get(0), display.getCurrentSelection()); + } + + public void testShowSuggestionsAutoSelectDisabled() { + SuggestBox box = new SuggestBox(); + SuggestionDisplay display = box.getSuggestionDisplay(); + SuggestOracle oracle = box.getSuggestOracle(); + + // Show some suggestions with auto select disabled. + List<Suggestion> suggestions = createSuggestions("test0", "test1", "test2"); + display.showSuggestions(box, suggestions, false, false, NULL_CALLBACK); + + // Nothing should be selected. + assertNull(display.getCurrentSelection()); + } + + public void testShowSuggestionsAutoSelectEnabled() { + SuggestBox box = new SuggestBox(); + SuggestionDisplay display = box.getSuggestionDisplay(); + SuggestOracle oracle = box.getSuggestOracle(); + + // Show some suggestions with auto select enabled. + List<Suggestion> suggestions = createSuggestions("test0", "test1", "test2"); + display.showSuggestions(box, suggestions, false, true, NULL_CALLBACK); + + // First item should be selected. + assertEquals(suggestions.get(0), display.getCurrentSelection()); + } + + /** + * Create a list of {@link Suggestion}. + * + * @param items the items to add to the list + * @return the list of suggestions + */ + protected List<Suggestion> createSuggestions(String... items) { + List<Suggestion> suggestions = new ArrayList<Suggestion>(); + for (String item : items) { + suggestions.add(new SimpleSuggestion(item)); + } + return suggestions; + } + + /** + * Create a new {@link SuggestionDisplay} to test. + * + * @return the {@link SuggestionDisplay} + */ + protected abstract SuggestionDisplay createSuggestionDisplay(); + + /** + * Create a new {@link SuggestBox}. + * + * @return the {@link SuggestBox} + */ + protected SuggestBox createSuggestBox() { + MultiWordSuggestOracle oracle = createOracle(); + return new SuggestBox(oracle, new TextBox(), createSuggestionDisplay()); + } + + private MultiWordSuggestOracle createOracle() { + MultiWordSuggestOracle oracle = new MultiWordSuggestOracle(); + oracle.add("test"); + oracle.add("test1"); + oracle.add("test2"); + oracle.add("test3"); + oracle.add("test4"); + oracle.add("john"); + return oracle; + } +}