View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.javascript.host.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE;
18  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
19  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
20  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
21  
22  import java.util.List;
23  
24  import com.gargoylesoftware.htmlunit.html.DomElement;
25  import com.gargoylesoftware.htmlunit.html.HtmlOption;
26  import com.gargoylesoftware.htmlunit.html.HtmlSelect;
27  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
28  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
29  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
30  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
31  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
32  import com.gargoylesoftware.htmlunit.javascript.host.dom.AbstractList;
33  
34  import net.sourceforge.htmlunit.corejs.javascript.Context;
35  import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
36  import net.sourceforge.htmlunit.corejs.javascript.Undefined;
37  
38  /**
39   * The JavaScript object for {@link HtmlSelect}.
40   *
41   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
42   * @author David K. Taylor
43   * @author Marc Guillemot
44   * @author Chris Erskine
45   * @author Ahmed Ashour
46   * @author Ronald Brill
47   * @author Carsten Steul
48   */
49  @JsxClass(domClass = HtmlSelect.class)
50  public class HTMLSelectElement extends FormField {
51  
52      private HTMLOptionsCollection optionsArray_;
53  
54      /** "Live" labels collection; has to be a member to have equality (==) working. */
55      private AbstractList labels_;
56  
57      /**
58       * Creates an instance.
59       */
60      @JsxConstructor({CHROME, FF, EDGE})
61      public HTMLSelectElement() {
62      }
63  
64      /**
65       * Initialize the object.
66       *
67       */
68      public void initialize() {
69          final HtmlSelect htmlSelect = getHtmlSelect();
70          htmlSelect.setScriptableObject(this);
71          if (optionsArray_ == null) {
72              optionsArray_ = new HTMLOptionsCollection(this);
73              optionsArray_.initialize(htmlSelect);
74          }
75      }
76  
77      /**
78       * Removes option at the specified index.
79       * @param index the index of the item to remove
80       */
81      @JsxFunction
82      public void remove(final int index) {
83          if (index < 0 && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
84              return;
85          }
86          final HTMLOptionsCollection options = getOptions();
87          if (index >= options.getLength() && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
88              return;
89          }
90  
91          getOptions().remove(index);
92      }
93  
94      /**
95       * Adds a new item to the list (optionally) before the specified item.
96       * @param newOptionObject the DomNode to insert
97       * @param beforeOptionObject for Firefox: the DomNode to insert the previous element before (null if at end),
98       * for Internet Explorer: the index where the element should be placed (optional).
99       */
100     @JsxFunction
101     public void add(final HTMLOptionElement newOptionObject, final Object beforeOptionObject) {
102         getOptions().add(newOptionObject, beforeOptionObject);
103     }
104 
105     /**
106      * {@inheritDoc}
107      */
108     @Override
109     public Object appendChild(final Object childObject) {
110         final Object object = super.appendChild(childObject);
111         getHtmlSelect().ensureSelectedIndex();
112         return object;
113     }
114 
115     /**
116      * {@inheritDoc}
117      */
118     @Override
119     public Object insertBeforeImpl(final Object[] args) {
120         final Object object = super.insertBeforeImpl(args);
121         getHtmlSelect().ensureSelectedIndex();
122         return object;
123     }
124 
125     /**
126      * Gets the item at the specified index.
127      * @param index the position of the option to retrieve
128      * @return the option
129      */
130     @JsxFunction
131     public Object item(final int index) {
132         final Object option = getOptions().item(index);
133         if (option == Undefined.instance) {
134             return null;
135         }
136         return option;
137     }
138 
139     /**
140      * Returns the type of this input.
141      * @return the type
142      */
143     @JsxGetter
144     public String getType() {
145         final String type;
146         if (getHtmlSelect().isMultipleSelectEnabled()) {
147             type = "select-multiple";
148         }
149         else {
150             type = "select-one";
151         }
152         return type;
153     }
154 
155     /**
156      * Returns the value of the {@code options} property.
157      * @return the {@code options} property
158      */
159     @JsxGetter
160     public HTMLOptionsCollection getOptions() {
161         if (optionsArray_ == null) {
162             initialize();
163         }
164         return optionsArray_;
165     }
166 
167     /**
168      * Returns the value of the {@code selectedIndex} property.
169      * @return the {@code selectedIndex} property
170      */
171     @JsxGetter
172     public int getSelectedIndex() {
173         return getHtmlSelect().getSelectedIndex();
174     }
175 
176     /**
177      * Sets the value of the {@code selectedIndex} property.
178      * @param index the new value
179      */
180     @JsxSetter
181     public void setSelectedIndex(final int index) {
182         getHtmlSelect().setSelectedIndex(index);
183     }
184 
185     /**
186      * Returns the actual value of the selected Option.
187      * @return the value
188      */
189     @Override
190     public String getValue() {
191         final HtmlSelect htmlSelect = getHtmlSelect();
192         final List<HtmlOption> selectedOptions = htmlSelect.getSelectedOptions();
193         if (selectedOptions.isEmpty()) {
194             return "";
195         }
196         return ((HTMLOptionElement) selectedOptions.get(0).getScriptableObject()).getValue();
197     }
198 
199     /**
200      * Returns the value of the {@code length} property.
201      * @return the {@code length} property
202      */
203     @JsxGetter
204     public int getLength() {
205         return getOptions().getLength();
206     }
207 
208     /**
209      * Removes options by reducing the {@code length} property.
210      * @param newLength the new {@code length} property value
211      */
212     @JsxSetter
213     public void setLength(final int newLength) {
214         getOptions().setLength(newLength);
215     }
216 
217     /**
218      * Returns the specified indexed property.
219      * @param index the index of the property
220      * @param start the scriptable object that was originally queried for this property
221      * @return the property
222      */
223     @Override
224     public Object get(final int index, final Scriptable start) {
225         if (getDomNodeOrNull() == null) {
226             return NOT_FOUND; // typically for the prototype
227         }
228         return getOptions().get(index, start);
229     }
230 
231     /**
232      * Sets the index property.
233      * @param index the index
234      * @param start the scriptable object that was originally invoked for this property
235      * @param newValue the new value
236      */
237     @Override
238     public void put(final int index, final Scriptable start, final Object newValue) {
239         getOptions().put(index, start, newValue);
240     }
241 
242     /**
243      * Returns the HTML select object.
244      * @return the HTML select object
245      */
246     private HtmlSelect getHtmlSelect() {
247         return (HtmlSelect) getDomNodeOrDie();
248     }
249 
250     /**
251      * Selects the option with the specified value.
252      * @param newValue the value of the option to select
253      */
254     @Override
255     public void setValue(final Object newValue) {
256         final String val = Context.toString(newValue);
257         getHtmlSelect().setSelectedAttribute(val, true, false);
258     }
259 
260     /**
261      * Returns the {@code size} attribute.
262      * @return the {@code size} attribute
263      */
264     @JsxGetter
265     public int getSize() {
266         int size = 0;
267         final String sizeAttribute = getDomNodeOrDie().getAttribute("size");
268         if (sizeAttribute != DomElement.ATTRIBUTE_NOT_DEFINED && sizeAttribute != DomElement.ATTRIBUTE_VALUE_EMPTY) {
269             try {
270                 size = Integer.parseInt(sizeAttribute);
271             }
272             catch (final Exception e) {
273                 //silently ignore
274             }
275         }
276         return size;
277     }
278 
279     /**
280      * Sets the {@code size} attribute.
281      * @param size the {@code size} attribute
282      */
283     @JsxSetter
284     public void setSize(final String size) {
285         getDomNodeOrDie().setAttribute("size", size);
286     }
287 
288     /**
289      * Returns {@code true} if the {@code multiple} attribute is set.
290      * @return {@code true} if the {@code multiple} attribute is set
291      */
292     @JsxGetter
293     public boolean isMultiple() {
294         return getDomNodeOrDie().hasAttribute("multiple");
295     }
296 
297     /**
298      * Sets or clears the {@code multiple} attribute.
299      * @param multiple {@code true} to set the {@code multiple} attribute, {@code false} to clear it
300      */
301     @JsxSetter
302     public void setMultiple(final boolean multiple) {
303         if (multiple) {
304             getDomNodeOrDie().setAttribute("multiple", "multiple");
305         }
306         else {
307             getDomNodeOrDie().removeAttribute("multiple");
308         }
309     }
310 
311     /**
312      * Returns the labels associated with the element.
313      * @return the labels associated with the element
314      */
315     @JsxGetter(CHROME)
316     public AbstractList getLabels() {
317         if (labels_ == null) {
318             labels_ = new LabelsHelper(getDomNodeOrDie());
319         }
320         return labels_;
321     }
322 
323     /**
324      * Checks whether the element has any constraints and whether it satisfies them.
325      * @return if the element is valid
326      */
327     @JsxFunction
328     public boolean checkValidity() {
329         return getDomNodeOrDie().isValid();
330     }
331 
332     /**
333      * Returns the {@code required} property.
334      * @return the {@code required} property
335      */
336     @JsxGetter
337     public boolean isRequired() {
338         return getDomNodeOrDie().isRequired();
339     }
340 
341     /**
342      * Sets the {@code required} property.
343      * @param required the new value
344      */
345     @JsxSetter
346     public void setRequired(final boolean required) {
347         getDomNodeOrDie().setRequired(required);
348     }
349 
350 }