Author: [email protected] Date: Wed Jan 14 10:05:05 2009 New Revision: 4451 Added: releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForSuggestBox.java Modified: releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestOracle.java releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
Log: Committing upgrades to suggest box review issue: http://gwt-code-reviews.appspot.com/2003 Review by:jlabanca Modified: releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java ============================================================================== --- releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java (original) +++ releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java Wed Jan 14 10:05:05 2009 @@ -64,5 +64,6 @@ addIssue(new VisualsForTreeEvents()); addIssue(new VisualsForWindowEvents()); addIssue(new VisualsForDialogBox()); + addIssue(new VisualsForSuggestBox()); } } Added: releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForSuggestBox.java ============================================================================== --- (empty file) +++ releases/1.6/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForSuggestBox.java Wed Jan 14 10:05:05 2009 @@ -0,0 +1,132 @@ +/* + * Copyright 2009 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.museum.client.defaultmuseum; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.museum.client.common.AbstractIssue; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.MultiWordSuggestOracle; +import com.google.gwt.user.client.ui.SuggestBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; + +import java.util.Arrays; +import java.util.List; + +/** + * Visual test for suggest box. + */ +public class VisualsForSuggestBox extends AbstractIssue { + MultiWordSuggestOracle girlsNames = new MultiWordSuggestOracle(); + MultiWordSuggestOracle girlsNamesWithDefault = new MultiWordSuggestOracle(); + + VisualsForSuggestBox() { + List<String> femaleNames = Arrays.asList("Jamie", "Jill", "Jackie", + "Susan", "Helen", "Emily", "Karen", "Emily", "Isabella", "Emma", "Ava", + "Madison", "Sophia", "Olivia", "Abigail", "Hannah", "Elizabeth", + "Addison", "Samantha", "Ashley", "Alyssa", "Mia", "Chloe", "Natalie", + "Sarah", "Alexis", "Grace", "Ella", "Brianna", "Hailey", "Taylor", + "Anna", "Kayla", "Lily", "Lauren", "Victoria", "Savannah", "Nevaeh", + "Jasmine", "Lillian", "Julia", "Sofia", "Kaylee", "Sydney", + "Gabriella", "Katherine", "Alexa", "Destiny", "Jessica", "Morgan", + "Kaitlyn", "Brooke", "Allison", "Makayla", "Avery", "Alexandra", + "Jocelyn"); + girlsNames.addAll(femaleNames); + girlsNamesWithDefault.addAll(femaleNames); + girlsNamesWithDefault.setDefaultSuggestionsFromText(femaleNames); + } + + /** + * This is the entry point method. + */ + public Widget createIssue() { + VerticalPanel panel = new VerticalPanel(); + + panel.add(new HTML( + "Select the show button, nothing should open <br/> Type 'e' and select the button again, now a suggestion list should open and close.")); + panel.add(manuallyShowAndHide()); + panel.add(new Label( + "Click on suggest box, it should open automatically. Check that First item is not selected")); + panel.add(suggestBoxWithDefault()); + return panel; + } + + @Override + public String getInstructions() { + return "Follow the directions above each suggest box"; + } + + @Override + public String getSummary() { + return "suggest box visuals"; + } + + @Override + public boolean hasCSS() { + return false; + } + + private Widget manuallyShowAndHide() { + FlexTable t = new FlexTable(); + t.getFlexCellFormatter().setColSpan(0, 0, 20); + final SuggestBox box = simpleSuggestBox(); + box.setAnimationEnabled(true); + t.setWidget(0, 0, box); + + Button showSuggestions = new Button( + "show suggestions, then hide after 2 seconds", new ClickHandler() { + public void onClick(ClickEvent event) { + box.showSuggestionList(); + new Timer() { + + @Override + public void run() { + box.hideSuggestionList(); + } + + }.schedule(2000); + } + }); + t.setWidget(1, 0, showSuggestions); + return t; + } + + private SuggestBox simpleSuggestBox() { + + SuggestBox b = new SuggestBox(girlsNames); + return b; + } + + private SuggestBox suggestBoxWithDefault() { + final SuggestBox b = new SuggestBox(girlsNamesWithDefault); + b.setSelectsFirstItem(false); + b.getTextBox().addMouseDownHandler(new MouseDownHandler() { + + public void onMouseDown(MouseDownEvent event) { + b.showSuggestionList(); + } + + }); + return b; + } +} \ No newline at end of file Modified: releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java ============================================================================== --- releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java (original) +++ releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java Wed Jan 14 10:05:05 2009 @@ -15,14 +15,10 @@ */ package com.google.gwt.user.client.ui; -import static com.google.gwt.event.dom.client.KeyCodes.KEY_DOWN; -import static com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER; -import static com.google.gwt.event.dom.client.KeyCodes.KEY_TAB; -import static com.google.gwt.event.dom.client.KeyCodes.KEY_UP; - import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.HandlesAllKeyEvents; import com.google.gwt.event.dom.client.HasAllKeyHandlers; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyPressEvent; @@ -35,10 +31,8 @@ import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.PopupPanel.AnimationType; import com.google.gwt.user.client.ui.SuggestOracle.Callback; import com.google.gwt.user.client.ui.SuggestOracle.Request; @@ -246,179 +240,16 @@ } } - /** - * A PopupPanel with a SuggestionMenu as its widget. The SuggestionMenu is - * placed in a PopupPanel so that it can be displayed at various positions - * around the SuggestBox's text field. Moreover, the SuggestionMenu needs to - * appear on top of any other widgets on the page, and the PopupPanel provides - * this behavior. - * - * A non-static member class is used because the popup uses the SuggestBox's - * SuggestionMenu as its widget, and the position of the SuggestBox's TextBox - * is needed in order to correctly position the popup. - */ - private class SuggestionPopup extends DecoratedPopupPanel { - private static final String STYLENAME_DEFAULT = "gwt-SuggestBoxPopup"; - - public SuggestionPopup() { - super(true, false, "suggestPopup"); - setWidget(suggestionMenu); - setStyleName(STYLENAME_DEFAULT); - setPreviewingAllNativeEvents(true); - } - - /** - * The default position of the SuggestPopup is directly below the - * SuggestBox's text box, with its left edge aligned with the left edge of - * the text box. Depending on the width and height of the popup and the - * distance from the text box to the bottom and right edges of the window, - * the popup may be displayed directly above the text box, and/or its right - * edge may be aligned with the right edge of the text box. - */ - public void showAlignedPopup() { - - // Set the position of the popup right before it is shown. - setPopupPositionAndShow(new PositionCallback() { - public void setPosition(int offsetWidth, int offsetHeight) { - - // Calculate left position for the popup. The computation for - // the left position is bidi-sensitive. - - int textBoxOffsetWidth = box.getOffsetWidth(); - - // Compute the difference between the popup's width and the - // textbox's width - int offsetWidthDiff = offsetWidth - textBoxOffsetWidth; - - int left; - - if (LocaleInfo.getCurrentLocale().isRTL()) { // RTL case - - int textBoxAbsoluteLeft = box.getAbsoluteLeft(); - - // Right-align the popup. Note that this computation is - // valid in the case where offsetWidthDiff is negative. - left = textBoxAbsoluteLeft - offsetWidthDiff; - - // If the suggestion popup is not as wide as the text box, always - // align to the right edge of the text box. Otherwise, figure out - // whether to right-align or left-align the popup. - if (offsetWidthDiff > 0) { - - // Make sure scrolling is taken into account, since - // box.getAbsoluteLeft() takes scrolling into account. - int windowRight = Window.getClientWidth() - + Window.getScrollLeft(); - int windowLeft = Window.getScrollLeft(); - - // Compute the left value for the right edge of the textbox - int textBoxLeftValForRightEdge = textBoxAbsoluteLeft - + textBoxOffsetWidth; - - // Distance from the right edge of the text box to the right edge - // of the window - int distanceToWindowRight = windowRight - - textBoxLeftValForRightEdge; - - // Distance from the right edge of the text box to the left edge - // of the window - int distanceFromWindowLeft = textBoxLeftValForRightEdge - - windowLeft; - - // If there is not enough space for the overflow of the popup's - // width to the right of the text box and there IS enough space - // for the overflow to the right of the text box, then left-align - // the popup. However, if there is not enough space on either - // side, stick with right-alignment. - if (distanceFromWindowLeft < offsetWidth - && distanceToWindowRight >= offsetWidthDiff) { - // Align with the left edge of the text box. - left = textBoxAbsoluteLeft; - } - } - } else { // LTR case - - // Left-align the popup. - left = box.getAbsoluteLeft(); - - // If the suggestion popup is not as wide as the text box, always - // align to the left edge of the text box. Otherwise, figure out - // whether to left-align or right-align the popup. - if (offsetWidthDiff > 0) { - // Make sure scrolling is taken into account, since - // box.getAbsoluteLeft() takes scrolling into account. - int windowRight = Window.getClientWidth() - + Window.getScrollLeft(); - int windowLeft = Window.getScrollLeft(); - - // Distance from the left edge of the text box to the right edge - // of the window - int distanceToWindowRight = windowRight - left; - - // Distance from the left edge of the text box to the left edge of - // the window - int distanceFromWindowLeft = left - windowLeft; - - // If there is not enough space for the overflow of the popup's - // width to the right of hte text box, and there IS enough space - // for the overflow to the left of the text box, then right-align - // the popup. However, if there is not enough space on either - // side, then stick with left-alignment. - if (distanceToWindowRight < offsetWidth - && distanceFromWindowLeft >= offsetWidthDiff) { - // Align with the right edge of the text box. - left -= offsetWidthDiff; - } - } - } - - // Calculate top position for the popup - - int top = box.getAbsoluteTop(); - - // Make sure scrolling is taken into account, since - // box.getAbsoluteTop() takes scrolling into account. - int windowTop = Window.getScrollTop(); - int windowBottom = Window.getScrollTop() + Window.getClientHeight(); - - // Distance from the top edge of the window to the top edge of the - // text box - int distanceFromWindowTop = top - windowTop; - - // Distance from the bottom edge of the window to the bottom edge of - // the text box - int distanceToWindowBottom = windowBottom - - (top + box.getOffsetHeight()); - - // If there is not enough space for the popup's height below the text - // box and there IS enough space for the popup's height above the text - // box, then then position the popup above the text box. However, if - // there is not enough space on either side, then stick with - // displaying the popup below the text box. - if (distanceToWindowBottom < offsetHeight - && distanceFromWindowTop >= offsetHeight) { - top -= offsetHeight; - } else { - // Position above the text box - top += box.getOffsetHeight(); - } - - setPopupPosition(left, top); - } - }); - } - } - private static final String STYLENAME_DEFAULT = "gwt-SuggestBox"; private int limit = 20; - private boolean selectsFirstItem = false; + private boolean selectsFirstItem = true; private SuggestOracle oracle; private String currentText; private final SuggestionMenu suggestionMenu; - private final SuggestionPopup suggestionPopup; + private final PopupPanel suggestionPopup; private final TextBoxBase box; - private final Callback callBack = new Callback() { + private final Callback callback = new Callback() { public void onSuggestionsReady(Request request, Response response) { showSuggestions(response.getSuggestions()); } @@ -458,8 +289,8 @@ // suggestionMenu must be created before suggestionPopup, because // suggestionMenu is suggestionPopup's widget suggestionMenu = new SuggestionMenu(true); - suggestionPopup = new SuggestionPopup(); - suggestionPopup.setAnimationType(AnimationType.ONE_WAY_CORNER); + suggestionPopup = createPopup(); + suggestionPopup.setAnimationType(AnimationType.ROLL_DOWN); addEventsToTextBox(); @@ -596,6 +427,13 @@ return box.getValue(); } + /** + * Hide current suggestions. + */ + public void hideSuggestionList() { + this.suggestionPopup.hide(); + } + public boolean isAnimationEnabled() { return suggestionPopup.isAnimationEnabled(); } @@ -692,6 +530,16 @@ } /** + * Show the current list of suggestions. + */ + public void showSuggestionList() { + if (isAttached()) { + currentText = null; + refreshSuggestions(); + } + } + + /** * <b>Affected Elements:</b> * <ul> * <li>-popup = The popup that appears with suggestions.</li> @@ -707,8 +555,39 @@ 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; + } + void showSuggestions(String query) { - oracle.requestSuggestions(new Request(query, limit), callBack); + if (query.length() == 0) { + oracle.requestDefaultSuggestions(new Request(null, limit), callback); + } else { + oracle.requestSuggestions(new Request(query, limit), callback); + } } private void addEventsToTextBox() { @@ -720,14 +599,14 @@ // are only relevant when choosing a suggestion. if (suggestionPopup.isAttached()) { switch (event.getNativeKeyCode()) { - case KEY_DOWN: + case KeyCodes.KEY_DOWN: suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() + 1); break; - case KEY_UP: + case KeyCodes.KEY_UP: suggestionMenu.selectItem(suggestionMenu.getSelectedItemIndex() - 1); break; - case KEY_ENTER: - case KEY_TAB: + case KeyCodes.KEY_ENTER: + case KeyCodes.KEY_TAB: if (suggestionMenu.getSelectedItemIndex() < 0) { suggestionPopup.hide(); } else { @@ -736,40 +615,21 @@ break; } } - fireEvent(event); + delegateEvent(SuggestBox.this, event); } public void onKeyPress(KeyPressEvent event) { - fireEvent(event); + delegateEvent(SuggestBox.this, event); } public void onKeyUp(KeyUpEvent event) { // After every user key input, refresh the popup's suggestions. refreshSuggestions(); - fireEvent(event); + delegateEvent(SuggestBox.this, event); } public void onValueChange(ValueChangeEvent<String> event) { - fireEvent(event); - } - - private void refreshSuggestions() { - // Get the raw text. - String text = box.getText(); - if (text.equals(currentText)) { - return; - } else { - currentText = text; - } - - if (text.length() == 0) { - // Optimization to avoid calling showSuggestions with an empty - // string - suggestionPopup.hide(); - suggestionMenu.clearItems(); - } else { - showSuggestions(text); - } + delegateEvent(SuggestBox.this, event); } } @@ -778,10 +638,30 @@ 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); } + private void refreshSuggestions() { + // Get the raw text. + String text = box.getText(); + if (text.equals(currentText)) { + return; + } else { + currentText = text; + } + showSuggestions(text); + } + private void setNewSelection(SuggestionMenuItem menuItem) { Suggestion curSuggestion = menuItem.getSuggestion(); currentText = curSuggestion.getReplacementString(); @@ -812,7 +692,6 @@ // and added to the menu. boolean isAnimationEnabled = suggestionPopup.isAnimationEnabled(); if (suggestionPopup.isAttached()) { - suggestionPopup.setAnimationEnabled(false); suggestionPopup.hide(); } @@ -835,7 +714,7 @@ suggestionMenu.selectItem(0); } - suggestionPopup.showAlignedPopup(); + suggestionPopup.showRelativeTo(getTextBox()); suggestionPopup.setAnimationEnabled(isAnimationEnabled); } else { suggestionPopup.hide(); Modified: releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestOracle.java ============================================================================== --- releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestOracle.java (original) +++ releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestOracle.java Wed Jan 14 10:05:05 2009 @@ -17,6 +17,7 @@ import com.google.gwt.user.client.rpc.IsSerializable; +import java.util.ArrayList; import java.util.Collection; /** @@ -26,12 +27,12 @@ * * @see SuggestBox */ -public abstract class SuggestOracle { - +public abstract class SuggestOracle { + private Response emptyResponse = new Response(new ArrayList<Suggestion>()); /** * Callback for {...@link com.google.gwt.user.client.ui.SuggestOracle}. Every * {...@link Request} should be associated with a callback that should be called - * after a {...@link Response} is generated. + * after a {...@link Response} is generated. */ public interface Callback { /** @@ -246,6 +247,24 @@ */ public boolean isDisplayStringHTML() { return false; + } + + /** + * Generate a {...@link Response} based on a default request. The request query + * must be null as it represents the results the oracle should return based on + * no query string. + * <p> + * After the {...@link Response} is created, it is passed into + * {...@link Callback#onSuggestionsReady(com.google.gwt.user.client.ui.SuggestOracle.Request, com.google.gwt.user.client.ui.SuggestOracle.Response)} + * . + * </p> + * + * @param request the request + * @param callback the callback to use for the response + */ + public void requestDefaultSuggestions(Request request, Callback callback) { + assert (request.query == null); + callback.onSuggestionsReady(request, emptyResponse); } /** Modified: releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java ============================================================================== --- releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java (original) +++ releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java Wed Jan 14 10:05:05 2009 @@ -17,6 +17,8 @@ import com.google.gwt.junit.client.GWTTestCase; +import java.util.Arrays; + /** * Tests for {...@link SuggestBoxTest}. */ @@ -47,6 +49,65 @@ assertFalse(box.isSuggestionListShowing()); box.showSuggestions("test"); assertTrue(box.isSuggestionListShowing()); + } + + public void testShowAndHide() { + SuggestBox box = createSuggestBox(); + assertFalse(box.isSuggestionListShowing()); + // should do nothing, box is not attached. + box.showSuggestionList(); + assertFalse(box.isSuggestionListShowing()); + + // Adds the suggest box to the root panel. + RootPanel.get().add(box); + assertFalse(box.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()); + + // Now, finally, should be true + box.setText("t"); + box.showSuggestionList(); + assertTrue(box.isSuggestionListShowing()); + + // Hides it for real this time. + box.hideSuggestionList(); + assertFalse(box.isSuggestionListShowing()); + } + + public void testDefaults() { + MultiWordSuggestOracle oracle = new MultiWordSuggestOracle(); + oracle.setDefaultSuggestionsFromText(Arrays.asList("A", "B")); + SuggestBox box = new SuggestBox(oracle); + 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()); + } + + public void testShowFirst() { + SuggestBox box = createSuggestBox(); + assertTrue(box.getSelectsFirstItem()); + SuggestBox box2 = createSuggestBox(); + assertTrue(box2.getSelectsFirstItem()); + box.setSelectsFirstItem(false); + assertFalse(box.getSelectsFirstItem()); + box.setText("t"); + box.showSuggestionList(); + // Todo(ecc) once event triggering is enabled, submit a return key to the + // text box and ensure that we see the correct behavior. + } + + @Override + public void gwtTearDown() throws Exception { + super.gwtTearDown(); + RootPanel.get().clear(); } protected SuggestBox createSuggestBox() { --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
