com.gargoylesoftware.htmlunit.javascript.host.HTMLOptionsCollection Maven / Gradle / Ivy
/*
* Copyright (c) 2002-2008 Gargoyle Software 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.gargoylesoftware.htmlunit.javascript.host;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import com.gargoylesoftware.htmlunit.WebAssert;
import com.gargoylesoftware.htmlunit.html.HTMLParser;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.javascript.ScriptableWithFallbackGetter;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
/**
* This is the array returned by the "options" property of Select.
*
* @version $Revision: 3026 $
* @author David K. Taylor
* @author Christian Sell
* @author Marc Guillemot
* @author Daniel Gredler
* @author Bruce Faulkner
* @author Ahmed Ashour
*/
public class HTMLOptionsCollection extends SimpleScriptable implements ScriptableWithFallbackGetter {
private static final long serialVersionUID = -4790255174217201235L;
private HtmlSelect htmlSelect_;
/**
* Creates an instance. JavaScript objects must have a default constructor.
*/
public HTMLOptionsCollection() {
// Empty.
}
/**
* Creates an instance.
* @param parentScope parent scope
*/
public HTMLOptionsCollection(final SimpleScriptable parentScope) {
setParentScope(parentScope);
setPrototype(getPrototype(getClass()));
}
/**
* Initializes this object.
* @param select the HtmlSelect that this object will retrieve elements from
*/
public void initialize(final HtmlSelect select) {
WebAssert.notNull("select", select);
htmlSelect_ = select;
}
/**
* Returns the object at the specified index.
*
* @param index the index
* @param start the object that get is being called on
* @return the object or NOT_FOUND
*/
@Override
public Object get(final int index, final Scriptable start) {
final Object response;
if (index < 0) {
throw Context.reportRuntimeError("Index or size is negative");
}
else if (index >= htmlSelect_.getOptionSize()) {
response = Context.getUndefinedValue();
}
else {
response = getScriptableFor(htmlSelect_.getOption(index));
}
return response;
}
/**
* If IE is emulated, and this class does not have the specified property, and the owning
* select *does* have the specified property, this method delegates the call to the parent
* select element.
*
* See {@link #getWithFallback(String)} for the corresponding getter behavior.
*
* @param name {@inheritDoc}
* @param start {@inheritDoc}
* @param value {@inheritDoc}
*/
@Override
public void put(final String name, final Scriptable start, final Object value) {
if (htmlSelect_ == null) {
// This object hasn't been initialized; it's probably being used as a prototype.
// Just pretend we didn't even see this invocation and let Rhino handle it.
super.put(name, start, value);
return;
}
final boolean ie = getWindow().getWebWindow().getWebClient().getBrowserVersion().isIE();
final HTMLSelectElement parent = (HTMLSelectElement) htmlSelect_.getScriptObject();
if (ie && !has(name, start) && ScriptableObject.hasProperty(parent, name)) {
ScriptableObject.putProperty(parent, name, value);
}
else {
super.put(name, start, value);
}
}
/**
* If IE is emulated, this method delegates the call to the parent select element.
*
* See {@link #put(String, Scriptable, Object)} for the corresponding setter behavior.
*
* @param name {@inheritDoc}
* @return {@inheritDoc}
*/
public Object getWithFallback(final String name) {
final boolean ie = getBrowserVersion().isIE();
if (ie) {
// If the name was NOT_FOUND on the prototype, then just drop through
// to search on the select element for IE only.
final HTMLSelectElement select = (HTMLSelectElement) htmlSelect_.getScriptObject();
return ScriptableObject.getProperty(select, name);
}
return NOT_FOUND;
}
/**
* Returns the object at the specified index.
*
* @param index the index
* @return the object or NOT_FOUND
*/
public Object jsxFunction_item(final int index) {
return get(index, null);
}
/**
* Sets the index property.
* @param index the index
* @param start the scriptable object that was originally invoked for this property
* @param newValue the new value
*/
@Override
public void put(final int index, final Scriptable start, final Object newValue) {
if (newValue == null) {
// Remove the indexed option.
htmlSelect_.removeOption(index);
}
else {
final HTMLOptionElement option = (HTMLOptionElement) newValue;
final HtmlOption htmlOption = (HtmlOption) option.getHtmlElementOrNull();
if (index >= jsxGet_length()) {
// Add a new option at the end.
htmlSelect_.appendOption(htmlOption);
}
else {
// Replace the indexed option.
htmlSelect_.replaceOption(index, htmlOption);
}
}
if (jsxGet_length() == 1 && !htmlSelect_.isMultipleSelectEnabled()) {
((HTMLSelectElement) htmlSelect_.getScriptObject()).jsxSet_selectedIndex(0);
}
}
/**
* Returns the number of elements in this array.
*
* @return the number of elements in the array
*/
public int jsxGet_length() {
return htmlSelect_.getOptionSize();
}
/**
* Changes the number of options: removes options if the new length
* is less than the current one else add new empty options to reach the
* new length.
* @param newLength the new length property value
*/
public void jsxSet_length(final int newLength) {
final int currentLength = htmlSelect_.getOptionSize();
if (currentLength > newLength) {
htmlSelect_.setOptionSize(newLength);
}
else {
for (int i = currentLength; i < newLength; i++) {
final HtmlOption option = (HtmlOption) HTMLParser.getFactory(HtmlOption.TAG_NAME).createElement(
htmlSelect_.getPage(), HtmlOption.TAG_NAME, null);
htmlSelect_.appendOption(option);
}
}
}
/**
* Adds a new item to the option collection.
*
* Implementation Note: The specification for the JavaScript add() method
* actually calls for the optional newIndex parameter to be an integer. However, the
* newIndex parameter is specified as an Object here rather than an int because of the
* way Rhino and HtmlUnit process optional parameters for the JavaScript method calls.
* If the newIndex parameter were specified as an int, then the Undefined value for an
* integer is specified as NaN (Not A Number, which is a Double value), but Rhino
* translates this value into 0 (perhaps correctly?) when converting NaN into an int.
* As a result, when the newIndex parameter is not specified, it is impossible to make
* a distinction between a caller of the form add(someObject) and add (someObject, 0).
* Since the behavior of these two call forms is different, the newIndex parameter is
* specified as an Object. If the newIndex parameter is not specified by the actual
* JavaScript code being run, then newIndex is of type org.mozilla.javascript.Undefined.
* If the newIndex parameter is specified, then it should be of type java.lang.Number and
* can be converted into an integer value.
*
* This method will call the {@link #put(int, Scriptable, Object)} method for actually
* adding the element to the collection.
*
* According to the
* Microsoft DHTML reference page for the JavaScript add() method of the options collection,
* the index parameter is specified as follows:
*
*
* -
* Optional. Integer that specifies the index position in the collection where the element is
* placed. If no value is given, the method places the element at the end of the collection.
*
*
*
* @param newOptionObject the DomNode to insert in the collection
* @param newIndex An optional parameter which specifies the index position in the
* collection where the element is placed. If no value is given, the method places
* the element at the end of the collection.
*
* @see #put(int, Scriptable, Object)
*/
public void jsxFunction_add(final Object newOptionObject, final Object newIndex) {
// If newIndex is undefined, then the item will be appended to the end of
// the list
int index = jsxGet_length();
// If newIndex was specified, then use it
if (newIndex instanceof Number) {
index = ((Number) newIndex).intValue();
}
// The put method either appends or replaces an object in the list,
// depending on the value of index
put(index, null, newOptionObject);
}
}