| /* |
| * Copyright 2008 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.dom.client.Document; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.dom.client.OptionElement; |
| import com.google.gwt.dom.client.SelectElement; |
| import com.google.gwt.event.dom.client.ChangeEvent; |
| import com.google.gwt.event.dom.client.ChangeHandler; |
| import com.google.gwt.event.dom.client.HasChangeHandlers; |
| import com.google.gwt.event.shared.HandlerRegistration; |
| import com.google.gwt.i18n.client.HasDirection.Direction; |
| import com.google.gwt.i18n.shared.BidiFormatter; |
| import com.google.gwt.i18n.shared.DirectionEstimator; |
| import com.google.gwt.i18n.shared.HasDirectionEstimator; |
| import com.google.gwt.i18n.shared.WordCountDirectionEstimator; |
| |
| /** |
| * A widget that presents a list of choices to the user, either as a list box or |
| * as a drop-down list. |
| * |
| * <p> |
| * <img class='gallery' src='doc-files/ListBox.png'/> |
| * </p> |
| * |
| * <h3>CSS Style Rules</h3> |
| * <ul class='css'> |
| * <li>.gwt-ListBox { }</li> |
| * </ul> |
| * |
| * <p> |
| * <h3>Example</h3> |
| * {@example com.google.gwt.examples.ListBoxExample} |
| * </p> |
| * |
| * <p> |
| * <h3>Built-in Bidi Text Support</h3> |
| * This widget is capable of automatically adjusting its direction according to |
| * its content. This feature is controlled by {@link #setDirectionEstimator}, |
| * and is off by default. |
| * </p> |
| * |
| * <h3>Use in UiBinder Templates</h3> |
| * <p> |
| * The items of a ListBox element are laid out in <g:item> elements. |
| * Each item contains text that will be added to the list of available |
| * items that will be shown, either in the drop down or list. (Note that |
| * the tags of the item elements are not capitalized. This is meant to |
| * signal that the item is not a runtime object, and so cannot have a |
| * <code>ui:field</code> attribute.) It is also possible to explicitly |
| * specify item's value using value attribute as shown below. |
| * <p> |
| * For example: |
| * |
| * <pre> |
| * <g:ListBox> |
| * <g:item> |
| * first |
| * </g:item> |
| * <g:item value='2'> |
| * second |
| * </g:item> |
| * </g:ListBox> |
| * </pre> |
| * <p> |
| * <h3>Important usage note</h3> |
| * <b>Subclasses should neither read nor write option text directly from the |
| * option elements created by this class, since such text may need to be wrapped |
| * in Unicode bidi formatting characters. They can use the getOptionText and/or |
| * setOptionText methods for this purpose instead.</b> |
| */ |
| @SuppressWarnings("deprecation") |
| public class ListBox extends FocusWidget implements SourcesChangeEvents, |
| HasChangeHandlers, HasName, HasDirectionEstimator { |
| |
| public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = |
| WordCountDirectionEstimator.get(); |
| |
| private static final String BIDI_ATTR_NAME = "bidiwrapped"; |
| |
| private static final int INSERT_AT_END = -1; |
| |
| /** |
| * Creates a ListBox widget that wraps an existing <select> element. |
| * |
| * This element must already be attached to the document. If the element is |
| * removed from the document, you must call |
| * {@link RootPanel#detachNow(Widget)}. |
| * |
| * @param element the element to be wrapped |
| * @return list box |
| */ |
| public static ListBox wrap(Element element) { |
| // Assert that the element is attached. |
| assert Document.get().getBody().isOrHasChild(element); |
| |
| ListBox listBox = new ListBox(element); |
| |
| // Mark it attached and remember it for cleanup. |
| listBox.onAttach(); |
| RootPanel.detachOnWindowClose(listBox); |
| |
| return listBox; |
| } |
| |
| private DirectionEstimator estimator; |
| |
| /** |
| * Creates an empty list box in single selection mode. |
| */ |
| public ListBox() { |
| super(Document.get().createSelectElement()); |
| setStyleName("gwt-ListBox"); |
| } |
| |
| /** |
| * Creates an empty list box. |
| * |
| * @param isMultipleSelect specifies if multiple selection is enabled |
| * @deprecated use {@link #setMultipleSelect(boolean)} instead. |
| */ |
| @Deprecated |
| public ListBox(boolean isMultipleSelect) { |
| this(); |
| setMultipleSelect(isMultipleSelect); |
| } |
| |
| /** |
| * This constructor may be used by subclasses to explicitly use an existing |
| * element. This element must be a <select> element. |
| * |
| * @param element the element to be used |
| */ |
| protected ListBox(Element element) { |
| super(element); |
| SelectElement.as(element); |
| } |
| |
| public HandlerRegistration addChangeHandler(ChangeHandler handler) { |
| return addDomHandler(handler, ChangeEvent.getType()); |
| } |
| |
| /** |
| * @deprecated Use {@link #addChangeHandler} instead |
| */ |
| @Deprecated |
| public void addChangeListener(ChangeListener listener) { |
| ListenerWrapper.WrappedChangeListener.add(this, listener); |
| } |
| |
| /** |
| * Adds an item to the list box. This method has the same effect as |
| * |
| * <pre> |
| * addItem(item, item) |
| * </pre> |
| * |
| * @param item the text of the item to be added |
| */ |
| public void addItem(String item) { |
| insertItem(item, INSERT_AT_END); |
| } |
| |
| /** |
| * Adds an item to the list box, specifying its direction. This method has the |
| * same effect as |
| * |
| * <pre> |
| * addItem(item, dir, item) |
| * </pre> |
| * |
| * @param item the text of the item to be added |
| * @param dir the item's direction |
| */ |
| public void addItem(String item, Direction dir) { |
| insertItem(item, dir, INSERT_AT_END); |
| } |
| |
| /** |
| * Adds an item to the list box, specifying an initial value for the item. |
| * |
| * @param item the text of the item to be added |
| * @param value the item's value, to be submitted if it is part of a |
| * {@link FormPanel}; cannot be <code>null</code> |
| */ |
| public void addItem(String item, String value) { |
| insertItem(item, value, INSERT_AT_END); |
| } |
| |
| /** |
| * Adds an item to the list box, specifying its direction and an initial value |
| * for the item. |
| * |
| * @param item the text of the item to be added |
| * @param dir the item's direction |
| * @param value the item's value, to be submitted if it is part of a |
| * {@link FormPanel}; cannot be <code>null</code> |
| */ |
| public void addItem(String item, Direction dir, String value) { |
| insertItem(item, dir, value, INSERT_AT_END); |
| } |
| |
| /** |
| * Removes all items from the list box. |
| */ |
| public void clear() { |
| getSelectElement().clear(); |
| } |
| |
| public DirectionEstimator getDirectionEstimator() { |
| return estimator; |
| } |
| |
| /** |
| * Gets the number of items present in the list box. |
| * |
| * @return the number of items |
| */ |
| public int getItemCount() { |
| return getSelectElement().getOptions().getLength(); |
| } |
| |
| /** |
| * Gets the text associated with the item at the specified index. |
| * |
| * @param index the index of the item whose text is to be retrieved |
| * @return the text associated with the item |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public String getItemText(int index) { |
| checkIndex(index); |
| return getOptionText(getSelectElement().getOptions().getItem(index)); |
| } |
| |
| /** |
| * Gets the text for currently selected item. If multiple items are selected, |
| * this method will return the text of the first selected item. |
| * |
| * @return the text for selected item, or {@code null} if none is selected |
| */ |
| public String getSelectedItemText() { |
| int index = getSelectedIndex(); |
| return index == -1 ? null : getItemText(index); |
| } |
| |
| public String getName() { |
| return getSelectElement().getName(); |
| } |
| |
| /** |
| * Gets the currently-selected item. If multiple items are selected, this |
| * method will return the first selected item ({@link #isItemSelected(int)} |
| * can be used to query individual items). |
| * |
| * @return the selected index, or <code>-1</code> if none is selected |
| */ |
| public int getSelectedIndex() { |
| return getSelectElement().getSelectedIndex(); |
| } |
| |
| /** |
| * Gets the value associated with the item at a given index. |
| * |
| * @param index the index of the item to be retrieved |
| * @return the item's associated value |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public String getValue(int index) { |
| checkIndex(index); |
| return getSelectElement().getOptions().getItem(index).getValue(); |
| } |
| |
| /** |
| * Gets the value for currently selected item. If multiple items are selected, |
| * this method will return the value of the first selected item. |
| * |
| * @return the value for selected item, or {@code null} if none is selected |
| */ |
| public String getSelectedValue() { |
| int index = getSelectedIndex(); |
| return index == -1 ? null : getValue(index); |
| } |
| |
| /** |
| * Gets the number of items that are visible. If only one item is visible, |
| * then the box will be displayed as a drop-down list. |
| * |
| * @return the visible item count |
| */ |
| public int getVisibleItemCount() { |
| return getSelectElement().getSize(); |
| } |
| |
| /** |
| * Inserts an item into the list box. Has the same effect as |
| * |
| * <pre> |
| * insertItem(item, item, index) |
| * </pre> |
| * |
| * @param item the text of the item to be inserted |
| * @param index the index at which to insert it |
| */ |
| public void insertItem(String item, int index) { |
| insertItem(item, item, index); |
| } |
| |
| /** |
| * Inserts an item into the list box, specifying its direction. Has the same |
| * effect as |
| * |
| * <pre> |
| * insertItem(item, dir, item, index) |
| * </pre> |
| * |
| * @param item the text of the item to be inserted |
| * @param dir the item's direction |
| * @param index the index at which to insert it |
| */ |
| public void insertItem(String item, Direction dir, int index) { |
| insertItem(item, dir, item, index); |
| } |
| |
| /** |
| * Inserts an item into the list box, specifying an initial value for the |
| * item. Has the same effect as |
| * |
| * <pre> |
| * insertItem(item, null, value, index) |
| * </pre> |
| * |
| * @param item the text of the item to be inserted |
| * @param value the item's value, to be submitted if it is part of a |
| * {@link FormPanel}. |
| * @param index the index at which to insert it |
| */ |
| public void insertItem(String item, String value, int index) { |
| insertItem(item, null, value, index); |
| } |
| |
| /** |
| * Inserts an item into the list box, specifying its direction and an initial |
| * value for the item. If the index is less than zero, or greater than or |
| * equal to the length of the list, then the item will be appended to the end |
| * of the list. |
| * |
| * @param item the text of the item to be inserted |
| * @param dir the item's direction. If {@code null}, the item is displayed in |
| * the widget's overall direction, or, if a direction estimator has |
| * been set, in the item's estimated direction. |
| * @param value the item's value, to be submitted if it is part of a |
| * {@link FormPanel}. |
| * @param index the index at which to insert it |
| */ |
| public void insertItem(String item, Direction dir, String value, int index) { |
| SelectElement select = getSelectElement(); |
| OptionElement option = Document.get().createOptionElement(); |
| setOptionText(option, item, dir); |
| option.setValue(value); |
| |
| int itemCount = select.getLength(); |
| if (index < 0 || index > itemCount) { |
| index = itemCount; |
| } |
| if (index == itemCount) { |
| select.add(option, null); |
| } else { |
| OptionElement before = select.getOptions().getItem(index); |
| select.add(option, before); |
| } |
| } |
| |
| /** |
| * Determines whether an individual list item is selected. |
| * |
| * @param index the index of the item to be tested |
| * @return <code>true</code> if the item is selected |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public boolean isItemSelected(int index) { |
| checkIndex(index); |
| return getSelectElement().getOptions().getItem(index).isSelected(); |
| } |
| |
| /** |
| * Gets whether this list allows multiple selection. |
| * |
| * @return <code>true</code> if multiple selection is allowed |
| */ |
| public boolean isMultipleSelect() { |
| return getSelectElement().isMultiple(); |
| } |
| |
| /** |
| * @deprecated Use the {@link HandlerRegistration#removeHandler} |
| * method on the object returned by {@link #addChangeHandler} instead |
| */ |
| @Deprecated |
| public void removeChangeListener(ChangeListener listener) { |
| ListenerWrapper.WrappedChangeListener.remove(this, listener); |
| } |
| |
| /** |
| * Removes the item at the specified index. |
| * |
| * @param index the index of the item to be removed |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public void removeItem(int index) { |
| checkIndex(index); |
| getSelectElement().remove(index); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * See note at |
| * {@link #setDirectionEstimator(com.google.gwt.i18n.shared.DirectionEstimator)}. |
| */ |
| public void setDirectionEstimator(boolean enabled) { |
| setDirectionEstimator(enabled ? DEFAULT_DIRECTION_ESTIMATOR : null); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * Note: this does not affect the direction of already-existing content. |
| */ |
| public void setDirectionEstimator(DirectionEstimator directionEstimator) { |
| estimator = directionEstimator; |
| } |
| |
| /** |
| * Sets whether an individual list item is selected. |
| * |
| * <p> |
| * Note that setting the selection programmatically does <em>not</em> cause |
| * the {@link ChangeHandler#onChange(ChangeEvent)} event to be fired. |
| * </p> |
| * |
| * @param index the index of the item to be selected or unselected |
| * @param selected <code>true</code> to select the item |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public void setItemSelected(int index, boolean selected) { |
| checkIndex(index); |
| getSelectElement().getOptions().getItem(index).setSelected(selected); |
| } |
| |
| /** |
| * Sets the text associated with the item at a given index. |
| * |
| * @param index the index of the item to be set |
| * @param text the item's new text |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public void setItemText(int index, String text) { |
| setItemText(index, text, null); |
| } |
| |
| /** |
| * Sets the text associated with the item at a given index. |
| * |
| * @param index the index of the item to be set |
| * @param text the item's new text |
| * @param dir the item's direction. |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public void setItemText(int index, String text, Direction dir) { |
| checkIndex(index); |
| if (text == null) { |
| throw new NullPointerException("Cannot set an option to have null text"); |
| } |
| setOptionText(getSelectElement().getOptions().getItem(index), text, dir); |
| } |
| |
| /** |
| * Sets whether this list allows multiple selections. <em>NOTE: |
| * Using this method can spuriously fail on Internet Explorer 6.0.</em> |
| * |
| * @param multiple <code>true</code> to allow multiple selections |
| */ |
| public void setMultipleSelect(boolean multiple) { |
| getSelectElement().setMultiple(multiple); |
| } |
| |
| public void setName(String name) { |
| getSelectElement().setName(name); |
| } |
| |
| /** |
| * Sets the currently selected index. |
| * |
| * After calling this method, only the specified item in the list will remain |
| * selected. For a ListBox with multiple selection enabled, see |
| * {@link #setItemSelected(int, boolean)} to select multiple items at a time. |
| * |
| * <p> |
| * Note that setting the selected index programmatically does <em>not</em> |
| * cause the {@link ChangeHandler#onChange(ChangeEvent)} event to be fired. |
| * </p> |
| * |
| * @param index the index of the item to be selected |
| */ |
| public void setSelectedIndex(int index) { |
| getSelectElement().setSelectedIndex(index); |
| } |
| |
| /** |
| * Sets the value associated with the item at a given index. This value can be |
| * used for any purpose, but is also what is passed to the server when the |
| * list box is submitted as part of a {@link FormPanel}. |
| * |
| * @param index the index of the item to be set |
| * @param value the item's new value; cannot be <code>null</code> |
| * @throws IndexOutOfBoundsException if the index is out of range |
| */ |
| public void setValue(int index, String value) { |
| checkIndex(index); |
| getSelectElement().getOptions().getItem(index).setValue(value); |
| } |
| |
| /** |
| * Sets the number of items that are visible. If only one item is visible, |
| * then the box will be displayed as a drop-down list. |
| * |
| * @param visibleItems the visible item count |
| */ |
| public void setVisibleItemCount(int visibleItems) { |
| getSelectElement().setSize(visibleItems); |
| } |
| |
| /** |
| * Retrieves the text of an option element. If the text was set by |
| * {@link #setOptionText} and was wrapped with Unicode bidi formatting |
| * characters, also removes those additional formatting characters. |
| * |
| * @param option an option element |
| * @return the element's text |
| */ |
| protected String getOptionText(OptionElement option) { |
| String text = option.getText(); |
| if (option.hasAttribute(BIDI_ATTR_NAME) && text.length() > 1) { |
| text = text.substring(1, text.length() - 1); |
| } |
| return text; |
| } |
| |
| /** |
| * <b>Affected Elements:</b> |
| * <ul> |
| * <li>-item# = the option at the specified index.</li> |
| * </ul> |
| * |
| * @see UIObject#onEnsureDebugId(String) |
| */ |
| @Override |
| protected void onEnsureDebugId(String baseID) { |
| super.onEnsureDebugId(baseID); |
| |
| // Set the id of each option |
| int numItems = getItemCount(); |
| for (int i = 0; i < numItems; i++) { |
| ensureDebugId(getSelectElement().getOptions().getItem(i), baseID, "item" |
| + i); |
| } |
| } |
| |
| /** |
| * Sets the text of an option element. If the direction of the text is |
| * opposite to the page's direction, also wraps it with Unicode bidi |
| * formatting characters to prevent garbling, and indicates that this was done |
| * by setting the option's <code>BIDI_ATTR_NAME</code> custom attribute. |
| * |
| * @param option an option element |
| * @param text text to be set to the element |
| * @param dir the text's direction. If {@code null} and direction estimation |
| * is turned off, direction is ignored. |
| */ |
| protected void setOptionText(OptionElement option, String text, |
| Direction dir) { |
| if (dir == null && estimator != null) { |
| dir = estimator.estimateDirection(text); |
| } |
| if (dir == null) { |
| option.setText(text); |
| option.removeAttribute(BIDI_ATTR_NAME); |
| } else { |
| String formattedText = |
| BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir( |
| dir, text, false /* isHtml */, false /* dirReset */); |
| option.setText(formattedText); |
| if (formattedText.length() > text.length()) { |
| option.setAttribute(BIDI_ATTR_NAME, ""); |
| } else { |
| option.removeAttribute(BIDI_ATTR_NAME); |
| } |
| } |
| } |
| |
| private void checkIndex(int index) { |
| if (index < 0 || index >= getItemCount()) { |
| throw new IndexOutOfBoundsException(); |
| } |
| } |
| |
| private SelectElement getSelectElement() { |
| return getElement().cast(); |
| } |
| } |