001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020: import com.google.gwt.user.client.Event;
021: import com.google.gwt.core.client.GWT;
022:
023: /**
024: * A widget that presents a list of choices to the user, either as a list box or
025: * as a drop-down list.
026: *
027: * <p>
028: * <img class='gallery' src='ListBox.png'/>
029: * </p>
030: *
031: * <h3>CSS Style Rules</h3>
032: * <ul class='css'>
033: * <li>.gwt-ListBox { }</li>
034: * </ul>
035: *
036: * <p>
037: * <h3>Example</h3>
038: * {@example com.google.gwt.examples.ListBoxExample}
039: * </p>
040: */
041: public class ListBox extends FocusWidget implements
042: SourcesChangeEvents, HasName {
043:
044: /**
045: * ListBox implementation for all browsers except Safari. This implementation
046: * relies on the JavaScript Select object and its 'options' array.
047: */
048: private static class Impl {
049:
050: public native void clear(Element select) /*-{
051: select.options.length = 0;
052: }-*/;
053:
054: public native int getItemCount(Element select) /*-{
055: return select.options.length;
056: }-*/;
057:
058: public native String getItemText(Element select, int index) /*-{
059: return select.options[index].text;
060: }-*/;
061:
062: public native String getItemValue(Element select, int index) /*-{
063: return select.options[index].value;
064: }-*/;
065:
066: public native boolean isItemSelected(Element select, int index) /*-{
067: return select.options[index].selected;
068: }-*/;
069:
070: public native void removeItem(Element select, int index) /*-{
071: select.options[index] = null;
072: }-*/;
073:
074: public native void setItemSelected(Element select, int index,
075: boolean selected) /*-{
076: select.options[index].selected = selected;
077: }-*/;
078:
079: public native void setValue(Element select, int index,
080: String value) /*-{
081: select.options[index].value = value;
082: }-*/;
083: }
084:
085: /**
086: * ListBox implementation for Safari. The 'options' array cannot be used
087: * due to a bug in the version of WebKit that ships with GWT
088: * (http://bugs.webkit.org/show_bug.cgi?id=10472).
089: * The 'children' array, which is common for all DOM elements in Safari,
090: * does not suffer from the same problem. Ideally, the 'children'
091: * array should be used in all of the traversal methods in the DOM classes.
092: * Unfortunately, due to a bug in Safari 2
093: * (http://bugs.webkit.org/show_bug.cgi?id=3330), this will not work.
094: * However, this bug does not cause problems in the case of <SELECT>
095: * elements, because their descendent elements are only one level deep.
096: */
097: private static class ImplSafari extends Impl {
098:
099: @Override
100: public native void clear(Element select) /*-{
101: select.innerText = '';
102: }-*/;
103:
104: @Override
105: public native int getItemCount(Element select) /*-{
106: return select.children.length;
107: }-*/;
108:
109: @Override
110: public native String getItemText(Element select, int index) /*-{
111: return select.children[index].text;
112: }-*/;
113:
114: @Override
115: public native String getItemValue(Element select, int index) /*-{
116: return select.children[index].value;
117: }-*/;
118:
119: @Override
120: public native boolean isItemSelected(Element select, int index) /*-{
121: return select.children[index].selected;
122: }-*/;
123:
124: @Override
125: public native void removeItem(Element select, int index) /*-{
126: select.removeChild(select.children[index]);
127: }-*/;
128:
129: @Override
130: public native void setItemSelected(Element select, int index,
131: boolean selected) /*-{
132: select.children[index].selected = selected;
133: }-*/;
134:
135: @Override
136: public native void setValue(Element select, int index,
137: String value) /*-{
138: select.children[index].value = value;
139: }-*/;
140: }
141:
142: private static final int INSERT_AT_END = -1;
143: private static final Impl impl = GWT.create(Impl.class);
144: private ChangeListenerCollection changeListeners;
145:
146: /**
147: * Creates an empty list box in single selection mode.
148: */
149: public ListBox() {
150: this (false);
151: }
152:
153: /**
154: * Creates an empty list box. The preferred way to enable multiple selections
155: * is to use this constructor rather than {@link #setMultipleSelect(boolean)}.
156: *
157: * @param isMultipleSelect specifies if multiple selection is enabled
158: */
159: public ListBox(boolean isMultipleSelect) {
160: super (DOM.createSelect(isMultipleSelect));
161: sinkEvents(Event.ONCHANGE);
162: setStyleName("gwt-ListBox");
163: }
164:
165: public void addChangeListener(ChangeListener listener) {
166: if (changeListeners == null) {
167: changeListeners = new ChangeListenerCollection();
168: }
169: changeListeners.add(listener);
170: }
171:
172: /**
173: * Adds an item to the list box. This method has the same effect as
174: *
175: * <pre>
176: * addItem(item, item)
177: * </pre>
178: *
179: * @param item the text of the item to be added
180: */
181: public void addItem(String item) {
182: insertItem(item, INSERT_AT_END);
183: }
184:
185: /**
186: * Adds an item to the list box, specifying an initial value for the item.
187: *
188: * @param item the text of the item to be added
189: * @param value the item's value, to be submitted if it is part of a
190: * {@link FormPanel}; cannot be <code>null</code>
191: */
192: public void addItem(String item, String value) {
193: insertItem(item, value, INSERT_AT_END);
194: }
195:
196: /**
197: * Removes all items from the list box.
198: */
199: public void clear() {
200: impl.clear(getElement());
201: }
202:
203: /**
204: * Gets the number of items present in the list box.
205: *
206: * @return the number of items
207: */
208: public int getItemCount() {
209: return impl.getItemCount(getElement());
210: }
211:
212: /**
213: * Gets the text associated with the item at the specified index.
214: *
215: * @param index the index of the item whose text is to be retrieved
216: * @return the text associated with the item
217: * @throws IndexOutOfBoundsException if the index is out of range
218: */
219: public String getItemText(int index) {
220: checkIndex(index);
221: return impl.getItemText(getElement(), index);
222: }
223:
224: public String getName() {
225: return DOM.getElementProperty(getElement(), "name");
226: }
227:
228: /**
229: * Gets the currently-selected item. If multiple items are selected, this
230: * method will return the first selected item ({@link #isItemSelected(int)}
231: * can be used to query individual items).
232: *
233: * @return the selected index, or <code>-1</code> if none is selected
234: */
235: public int getSelectedIndex() {
236: return DOM.getElementPropertyInt(getElement(), "selectedIndex");
237: }
238:
239: /**
240: * Gets the value associated with the item at a given index.
241: *
242: * @param index the index of the item to be retrieved
243: * @return the item's associated value
244: * @throws IndexOutOfBoundsException if the index is out of range
245: */
246: public String getValue(int index) {
247: checkIndex(index);
248: return impl.getItemValue(getElement(), index);
249: }
250:
251: /**
252: * Gets the number of items that are visible. If only one item is visible,
253: * then the box will be displayed as a drop-down list.
254: *
255: * @return the visible item count
256: */
257: public int getVisibleItemCount() {
258: return DOM.getElementPropertyInt(getElement(), "size");
259: }
260:
261: /**
262: * Inserts an item into the list box. Has the same effect as
263: *
264: * <pre>
265: * insertItem(item, item, index)
266: * </pre>
267: *
268: * @param item the text of the item to be inserted
269: * @param index the index at which to insert it
270: */
271: public void insertItem(String item, int index) {
272: insertItem(item, item, index);
273: }
274:
275: /**
276: * Inserts an item into the list box, specifying an initial value for the
277: * item. If the index is less than zero, or greater than or equal to
278: * the length of the list, then the item will be appended to the end of
279: * the list.
280: *
281: * @param item the text of the item to be inserted
282: * @param value the item's value, to be submitted if it is part of a
283: * {@link FormPanel}.
284: * @param index the index at which to insert it
285: */
286: public void insertItem(String item, String value, int index) {
287: DOM.insertListItem(getElement(), item, value, index);
288: }
289:
290: /**
291: * Determines whether an individual list item is selected.
292: *
293: * @param index the index of the item to be tested
294: * @return <code>true</code> if the item is selected
295: * @throws IndexOutOfBoundsException if the index is out of range
296: */
297: public boolean isItemSelected(int index) {
298: checkIndex(index);
299: return impl.isItemSelected(getElement(), index);
300: }
301:
302: /**
303: * Gets whether this list allows multiple selection.
304: *
305: * @return <code>true</code> if multiple selection is allowed
306: */
307: public boolean isMultipleSelect() {
308: return DOM.getElementPropertyBoolean(getElement(), "multiple");
309: }
310:
311: @Override
312: public void onBrowserEvent(Event event) {
313: if (DOM.eventGetType(event) == Event.ONCHANGE) {
314: if (changeListeners != null) {
315: changeListeners.fireChange(this );
316: }
317: } else {
318: super .onBrowserEvent(event);
319: }
320: }
321:
322: public void removeChangeListener(ChangeListener listener) {
323: if (changeListeners != null) {
324: changeListeners.remove(listener);
325: }
326: }
327:
328: /**
329: * Removes the item at the specified index.
330: *
331: * @param index the index of the item to be removed
332: * @throws IndexOutOfBoundsException if the index is out of range
333: */
334: public void removeItem(int index) {
335: checkIndex(index);
336: impl.removeItem(getElement(), index);
337: }
338:
339: /**
340: * Sets whether an individual list item is selected.
341: *
342: * <p>
343: * Note that setting the selection programmatically does <em>not</em> cause
344: * the {@link ChangeListener#onChange(Widget)} event to be fired.
345: * </p>
346: *
347: * @param index the index of the item to be selected or unselected
348: * @param selected <code>true</code> to select the item
349: * @throws IndexOutOfBoundsException if the index is out of range
350: */
351: public void setItemSelected(int index, boolean selected) {
352: checkIndex(index);
353: impl.setItemSelected(getElement(), index, selected);
354: }
355:
356: /**
357: * Sets the text associated with the item at a given index.
358: *
359: * @param index the index of the item to be set
360: * @param text the item's new text
361: * @throws IndexOutOfBoundsException if the index is out of range
362: */
363: public void setItemText(int index, String text) {
364: checkIndex(index);
365: if (text == null) {
366: throw new NullPointerException(
367: "Cannot set an option to have null text");
368: }
369: DOM.setOptionText(getElement(), text, index);
370: }
371:
372: /**
373: * Sets whether this list allows multiple selections. <em>NOTE: The preferred
374: * way of enabling multiple selections in a list box is by using the
375: * {@link #ListBox(boolean)} constructor. Using this method can spuriously
376: * fail on Internet Explorer 6.0.</em>
377: *
378: * @param multiple <code>true</code> to allow multiple selections
379: */
380: public void setMultipleSelect(boolean multiple) {
381: // TODO: we can remove the above doc admonition once we address issue 1007
382: DOM.setElementPropertyBoolean(getElement(), "multiple",
383: multiple);
384: }
385:
386: public void setName(String name) {
387: DOM.setElementProperty(getElement(), "name", name);
388: }
389:
390: /**
391: * Sets the currently selected index.
392: *
393: * After calling this method, only the specified item in the list will
394: * remain selected. For a ListBox with multiple selection enabled, see
395: * {@link #setItemSelected(int, boolean)} to select multiple items at a time.
396: *
397: * <p>
398: * Note that setting the selected index programmatically does <em>not</em>
399: * cause the {@link ChangeListener#onChange(Widget)} event to be fired.
400: * </p>
401: *
402: * @param index the index of the item to be selected
403: */
404: public void setSelectedIndex(int index) {
405: DOM.setElementPropertyInt(getElement(), "selectedIndex", index);
406: }
407:
408: /**
409: * Sets the value associated with the item at a given index. This value can be
410: * used for any purpose, but is also what is passed to the server when the
411: * list box is submitted as part of a {@link FormPanel}.
412: *
413: * @param index the index of the item to be set
414: * @param value the item's new value; cannot be <code>null</code>
415: * @throws IndexOutOfBoundsException if the index is out of range
416: */
417: public void setValue(int index, String value) {
418: checkIndex(index);
419: impl.setValue(getElement(), index, value);
420: }
421:
422: /**
423: * Sets the number of items that are visible. If only one item is visible,
424: * then the box will be displayed as a drop-down list.
425: *
426: * @param visibleItems the visible item count
427: */
428: public void setVisibleItemCount(int visibleItems) {
429: DOM.setElementPropertyInt(getElement(), "size", visibleItems);
430: }
431:
432: private void checkIndex(int index) {
433: if (index < 0 || index >= getItemCount()) {
434: throw new IndexOutOfBoundsException();
435: }
436: }
437: }
|