001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit.javascript;
039:
040: import org.mozilla.javascript.Context;
041: import org.mozilla.javascript.Scriptable;
042: import org.mozilla.javascript.ScriptableObject;
043:
044: import com.gargoylesoftware.htmlunit.Assert;
045: import com.gargoylesoftware.htmlunit.html.HTMLParser;
046: import com.gargoylesoftware.htmlunit.html.HtmlOption;
047: import com.gargoylesoftware.htmlunit.html.HtmlSelect;
048: import com.gargoylesoftware.htmlunit.javascript.host.HTMLSelectElement;
049: import com.gargoylesoftware.htmlunit.javascript.host.Option;
050:
051: /**
052: * This is the array returned by the "options" property of Select.
053: *
054: * @version $Revision: 2132 $
055: * @author David K. Taylor
056: * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
057: * @author Marc Guillemot
058: * @author Daniel Gredler
059: * @author Bruce Faulkner
060: * @author Ahmed Ashour
061: */
062: public class HTMLOptionsCollection extends SimpleScriptable implements
063: ScriptableWithFallbackGetter {
064:
065: private static final long serialVersionUID = -4790255174217201235L;
066: private HtmlSelect htmlSelect_;
067:
068: /**
069: * Creates an instance. JavaScript objects must have a default constructor.
070: */
071: public HTMLOptionsCollection() {
072: // Empty.
073: }
074:
075: /**
076: * Creates an instance.
077: * @param parentScope parent scope
078: */
079: public HTMLOptionsCollection(final SimpleScriptable parentScope) {
080: setParentScope(parentScope);
081: setPrototype(getPrototype(getClass()));
082: }
083:
084: /**
085: * Initializes this object.
086: * @param select The HtmlSelect that this object will retrieve elements from.
087: */
088: public void initialize(final HtmlSelect select) {
089: Assert.notNull("select", select);
090: htmlSelect_ = select;
091: }
092:
093: /**
094: * Returns the object at the specified index.
095: *
096: * @param index The index
097: * @param start The object that get is being called on.
098: * @return The object or NOT_FOUND
099: */
100: public Object get(final int index, final Scriptable start) {
101: final Object response;
102: if (index < 0) {
103: throw Context
104: .reportRuntimeError("Index or size is negative");
105: } else if (index >= htmlSelect_.getOptionSize()) {
106: response = Context.getUndefinedValue();
107: } else {
108: response = getScriptableFor(htmlSelect_.getOption(index));
109: }
110:
111: return response;
112: }
113:
114: /**
115: * <p>If IE is emulated, and this class does not have the specified property, and the owning
116: * select *does* have the specified property, this method delegates the call to the parent
117: * select element.</p>
118: *
119: * <p>See {@link #getWithFallback(String)} for the corresponding getter behavior.</p>
120: *
121: * @param name {@inheritDoc}
122: * @param start {@inheritDoc}
123: * @param value {@inheritDoc}
124: */
125: public void put(final String name, final Scriptable start,
126: final Object value) {
127:
128: if (htmlSelect_ == null) {
129: // This object hasn't been initialized; it's probably being used as a prototype.
130: // Just pretend we didn't even see this invocation and let Rhino handle it.
131: super .put(name, start, value);
132: return;
133: }
134:
135: final boolean ie = getWindow().getWebWindow().getWebClient()
136: .getBrowserVersion().isIE();
137: final HTMLSelectElement parent = (HTMLSelectElement) htmlSelect_
138: .getScriptObject();
139:
140: if (ie && !has(name, start)
141: && ScriptableObject.hasProperty(parent, name)) {
142: ScriptableObject.putProperty(parent, name, value);
143: } else {
144: super .put(name, start, value);
145: }
146: }
147:
148: /**
149: * <p>If IE is emulated, this method delegates the call to the parent select element.</p>
150: *
151: * <p>See {@link #put(String, Scriptable, Object)} for the corresponding setter behavior.</p>
152: *
153: * @param name {@inheritDoc}
154: * @return {@inheritDoc}
155: */
156: public Object getWithFallback(final String name) {
157: final boolean ie = getWindow().getWebWindow().getWebClient()
158: .getBrowserVersion().isIE();
159: if (ie) {
160: // If the name was NOT_FOUND on the prototype, then just drop through
161: // to search on the select element for IE only.
162: final HTMLSelectElement select = (HTMLSelectElement) htmlSelect_
163: .getScriptObject();
164: return ScriptableObject.getProperty(select, name);
165: }
166: return NOT_FOUND;
167: }
168:
169: /**
170: * <p>Return the object at the specified index.</p>
171: *
172: * @param index The index
173: * @return The object or NOT_FOUND
174: */
175: public Object jsxFunction_item(final int index) {
176: return get(index, null);
177: }
178:
179: /**
180: * Set the index property
181: * @param index The index
182: * @param start The scriptable object that was originally invoked for this property
183: * @param newValue The new value
184: */
185: public void put(final int index, final Scriptable start,
186: final Object newValue) {
187: if (newValue == null) {
188: // Remove the indexed option.
189: htmlSelect_.removeOption(index);
190: } else {
191: final Option option = (Option) newValue;
192: final HtmlOption htmlOption = (HtmlOption) option
193: .getHtmlElementOrNull();
194: if (index >= jsxGet_length()) {
195: // Add a new option at the end.
196: htmlSelect_.appendOption(htmlOption);
197: } else {
198: // Replace the indexed option.
199: htmlSelect_.replaceOption(index, htmlOption);
200: }
201: }
202: if (jsxGet_length() == 1
203: && !htmlSelect_.isMultipleSelectEnabled()) {
204: ((HTMLSelectElement) htmlSelect_.getScriptObject())
205: .jsxSet_selectedIndex(0);
206: }
207: }
208:
209: /**
210: * <p>Return the number of elements in this array</p>
211: *
212: * @return The number of elements in the array
213: */
214: public int jsxGet_length() {
215: return htmlSelect_.getOptionSize();
216: }
217:
218: /**
219: * Change the number of options: removes options if the new length
220: * is less than the current one else add new empty options to reach the
221: * new length.
222: * @param newLength The new length property value
223: */
224: public void jsxSet_length(final int newLength) {
225: final int currentLength = htmlSelect_.getOptionSize();
226: if (currentLength > newLength) {
227: htmlSelect_.setOptionSize(newLength);
228: } else {
229: for (int i = currentLength; i < newLength; i++) {
230: final HtmlOption option = (HtmlOption) HTMLParser
231: .getFactory(HtmlOption.TAG_NAME).createElement(
232: htmlSelect_.getPage(),
233: HtmlOption.TAG_NAME, null);
234: htmlSelect_.appendOption(option);
235: }
236: }
237: }
238:
239: /**
240: * Add a new item to the option collection
241: *
242: * <p>
243: * <b><i>Implementation Note:</i></b> The specification for the JavaScript add() method
244: * actually calls for the optional newIndex parameter to be an integer. However, the
245: * newIndex parameter is specified as an Object here rather than an int because of the
246: * way Rhino and HtmlUnit process optional parameters for the JavaScript method calls.
247: * If the newIndex parameter were specified as an int, then the Undefined value for an
248: * integer is specified as NaN (Not A Number, which is a Double value), but Rhino
249: * translates this value into 0 (perhaps correctly?) when converting NaN into an int.
250: * As a result, when the newIndex parameter is not specified, it is impossible to make
251: * a distinction between a caller of the form add(someObject) and add (someObject, 0).
252: * Since the behavior of these two call forms is different, the newIndex parameter is
253: * specified as an Object. If the newIndex parameter is not specified by the actual
254: * JavaScript code being run, then newIndex is of type org.mozilla.javascript.Undefined.
255: * If the newIndex parameter is specified, then it should be of type java.lang.Number and
256: * can be converted into an integer value.
257: * </p>
258: * <p>
259: * This method will call the {@link #put} method for actually adding the element to the
260: * collection.
261: * </p>
262: * <p>
263: * According to
264: * <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/add.asp">the
265: * Microsoft DHTML reference page for the JavaScript add() method of the options collection</a>,
266: * the index parameter is specified as follows:
267: * <dl>
268: * <dt></dt>
269: * <dd>
270: * <i>Optional. Integer that specifies the index position in the collection where the element is
271: * placed. If no value is given, the method places the element at the end of the collection.</i>
272: * </dl>
273: * </p>
274: *
275: * @param newOptionObject The DomNode to insert in the collection
276: * @param newIndex An optional parameter which specifies the index position in the
277: * collection where the element is placed. If no value is given, the method places
278: * the element at the end of the collection.
279: *
280: * @see #put
281: */
282: public void jsxFunction_add(final Object newOptionObject,
283: final Object newIndex) {
284: // If newIndex is undefined, then the item will be appended to the end of
285: // the list
286: int index = jsxGet_length();
287:
288: // If newIndex was specified, then use it
289: if (newIndex instanceof Number) {
290: index = ((Number) newIndex).intValue();
291: }
292:
293: // The put method either appends or replaces an object in the list,
294: // depending on the value of index
295: put(index, null, newOptionObject);
296: }
297: }
|