View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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    * https://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 org.htmlunit.javascript.host.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE;
18  
19  import java.util.List;
20  
21  import org.htmlunit.corejs.javascript.Scriptable;
22  import org.htmlunit.html.HtmlOption;
23  import org.htmlunit.html.HtmlSelect;
24  import org.htmlunit.javascript.JavaScriptEngine;
25  import org.htmlunit.javascript.configuration.JsxClass;
26  import org.htmlunit.javascript.configuration.JsxConstructor;
27  import org.htmlunit.javascript.configuration.JsxFunction;
28  import org.htmlunit.javascript.configuration.JsxGetter;
29  import org.htmlunit.javascript.configuration.JsxSetter;
30  import org.htmlunit.javascript.configuration.JsxSymbol;
31  import org.htmlunit.javascript.host.dom.Node;
32  import org.htmlunit.javascript.host.dom.NodeList;
33  
34  /**
35   * The JavaScript object for {@link HtmlSelect}.
36   *
37   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
38   * @author David K. Taylor
39   * @author Marc Guillemot
40   * @author Chris Erskine
41   * @author Ahmed Ashour
42   * @author Ronald Brill
43   * @author Carsten Steul
44   */
45  @JsxClass(domClass = HtmlSelect.class)
46  public class HTMLSelectElement extends HTMLElement {
47  
48      private HTMLOptionsCollection optionsArray_;
49  
50      /** "Live" labels collection; has to be a member to have equality (==) working. */
51      private NodeList labels_;
52  
53      /**
54       * JavaScript constructor.
55       */
56      @Override
57      @JsxConstructor
58      public void jsConstructor() {
59          super.jsConstructor();
60      }
61  
62      /**
63       * Initialize the object.
64       *
65       */
66      public void initialize() {
67          final HtmlSelect htmlSelect = getDomNodeOrDie();
68          htmlSelect.setScriptableObject(this);
69          if (optionsArray_ == null) {
70              optionsArray_ = new HTMLOptionsCollection(this);
71              optionsArray_.initialize(htmlSelect);
72          }
73      }
74  
75      /**
76       * {@inheritDoc}
77       */
78      @Override
79      public HtmlSelect getDomNodeOrDie() {
80          return (HtmlSelect) super.getDomNodeOrDie();
81      }
82  
83      /**
84       * Removes option at the specified index.
85       * @param index the index of the item to remove
86       */
87      @JsxFunction
88      public void remove(final int index) {
89          if (index < 0 && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
90              return;
91          }
92          final HTMLOptionsCollection options = getOptions();
93          if (index >= options.getLength() && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
94              return;
95          }
96  
97          getOptions().remove(index);
98      }
99  
100     /**
101      * Adds a new item to the list (optionally) before the specified item.
102      * @param newOptionObject the DomNode to insert
103      * @param beforeOptionObject the DomNode to insert the previous element before (null if at end).
104      */
105     @JsxFunction
106     public void add(final HTMLOptionElement newOptionObject, final Object beforeOptionObject) {
107         getOptions().add(newOptionObject, beforeOptionObject);
108     }
109 
110     /**
111      * {@inheritDoc}
112      */
113     @Override
114     public Node appendChild(final Object childObject) {
115         final Node node = super.appendChild(childObject);
116         getDomNodeOrDie().ensureSelectedIndex();
117         return node;
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     public Node insertBeforeImpl(final Object[] args) {
125         final Node node = super.insertBeforeImpl(args);
126         getDomNodeOrDie().ensureSelectedIndex();
127         return node;
128     }
129 
130     /**
131      * Gets the item at the specified index.
132      * @param index the position of the option to retrieve
133      * @return the option
134      */
135     @JsxFunction
136     public Object item(final int index) {
137         final Object option = getOptions().item(index);
138         if (JavaScriptEngine.isUndefined(option)) {
139             return null;
140         }
141         return option;
142     }
143 
144     /**
145      * Returns the type of this input.
146      * @return the type
147      */
148     @JsxGetter
149     public String getType() {
150         final String type;
151         if (getDomNodeOrDie().isMultipleSelectEnabled()) {
152             type = "select-multiple";
153         }
154         else {
155             type = "select-one";
156         }
157         return type;
158     }
159 
160     /**
161      * Returns the value of the {@code options} property.
162      * @return the {@code options} property
163      */
164     @JsxGetter
165     public HTMLOptionsCollection getOptions() {
166         if (optionsArray_ == null) {
167             initialize();
168         }
169         return optionsArray_;
170     }
171 
172     /**
173      * Returns the value of the {@code selectedIndex} property.
174      * @return the {@code selectedIndex} property
175      */
176     @JsxGetter
177     public int getSelectedIndex() {
178         return getDomNodeOrDie().getSelectedIndex();
179     }
180 
181     /**
182      * Sets the value of the {@code selectedIndex} property.
183      * @param index the new value
184      */
185     @JsxSetter
186     public void setSelectedIndex(final int index) {
187         getDomNodeOrDie().setSelectedIndex(index);
188     }
189 
190     /**
191      * Returns the actual value of the selected Option.
192      * @return the value
193      */
194     @Override
195     @JsxGetter
196     public String getValue() {
197         final List<HtmlOption> selectedOptions = getDomNodeOrDie().getSelectedOptions();
198         if (selectedOptions.isEmpty()) {
199             return "";
200         }
201         return ((HTMLOptionElement) selectedOptions.get(0).getScriptableObject()).getValue();
202     }
203 
204     /**
205      * Returns the value of the {@code length} property.
206      * @return the {@code length} property
207      */
208     @JsxGetter
209     public int getLength() {
210         return getOptions().getLength();
211     }
212 
213     /**
214      * Removes options by reducing the {@code length} property.
215      * @param newLength the new {@code length} property value
216      */
217     @JsxSetter
218     public void setLength(final int newLength) {
219         getOptions().setLength(newLength);
220     }
221 
222     /**
223      * Returns the specified indexed property.
224      * @param index the index of the property
225      * @param start the scriptable object that was originally queried for this property
226      * @return the property
227      */
228     @Override
229     public Object get(final int index, final Scriptable start) {
230         if (getDomNodeOrNull() == null) {
231             return NOT_FOUND; // typically for the prototype
232         }
233         return getOptions().get(index, start);
234     }
235 
236     /**
237      * Sets the index property.
238      * @param index the index
239      * @param start the scriptable object that was originally invoked for this property
240      * @param newValue the new value
241      */
242     @Override
243     public void put(final int index, final Scriptable start, final Object newValue) {
244         getOptions().put(index, start, newValue);
245     }
246 
247     /**
248      * Selects the option with the specified value.
249      * @param newValue the value of the option to select
250      */
251     @Override
252     @JsxSetter
253     public void setValue(final Object newValue) {
254         final String val = JavaScriptEngine.toString(newValue);
255         getDomNodeOrDie().setSelectedAttribute(val, true, false);
256     }
257 
258     /**
259      * Returns the {@code size} attribute.
260      * @return the {@code size} attribute
261      */
262     @JsxGetter
263     public int getSize() {
264         return getDomNodeOrDie().getSize();
265     }
266 
267     /**
268      * Sets the {@code size} attribute.
269      * @param size the {@code size} attribute
270      */
271     @JsxSetter
272     public void setSize(final String size) {
273         getDomNodeOrDie().setAttribute("size", size);
274     }
275 
276     /**
277      * Returns {@code true} if the {@code multiple} attribute is set.
278      * @return {@code true} if the {@code multiple} attribute is set
279      */
280     @JsxGetter
281     public boolean isMultiple() {
282         return getDomNodeOrDie().hasAttribute("multiple");
283     }
284 
285     /**
286      * Sets or clears the {@code multiple} attribute.
287      * @param multiple {@code true} to set the {@code multiple} attribute, {@code false} to clear it
288      */
289     @JsxSetter
290     public void setMultiple(final boolean multiple) {
291         if (multiple) {
292             getDomNodeOrDie().setAttribute("multiple", "multiple");
293         }
294         else {
295             getDomNodeOrDie().removeAttribute("multiple");
296         }
297     }
298 
299     /**
300      * Returns the labels associated with the element.
301      * @return the labels associated with the element
302      */
303     @JsxGetter
304     public NodeList getLabels() {
305         if (labels_ == null) {
306             labels_ = new LabelsNodeList(getDomNodeOrDie());
307         }
308         return labels_;
309     }
310 
311     /**
312      * Returns the {@code required} property.
313      * @return the {@code required} property
314      */
315     @JsxGetter
316     public boolean isRequired() {
317         return getDomNodeOrDie().isRequired();
318     }
319 
320     /**
321      * Sets the {@code required} property.
322      * @param required the new value
323      */
324     @JsxSetter
325     public void setRequired(final boolean required) {
326         getDomNodeOrDie().setRequired(required);
327     }
328 
329     /**
330      * {@inheritDoc}
331      */
332     @JsxGetter
333     @Override
334     public String getName() {
335         return super.getName();
336     }
337 
338     /**
339      * {@inheritDoc}
340      */
341     @JsxSetter
342     @Override
343     public void setName(final String newName) {
344         super.setName(newName);
345     }
346 
347     /**
348      * {@inheritDoc} Overridden to modify browser configurations.
349      */
350     @Override
351     @JsxGetter
352     public boolean isDisabled() {
353         return super.isDisabled();
354     }
355 
356     /**
357      * {@inheritDoc} Overridden to modify browser configurations.
358      */
359     @Override
360     @JsxSetter
361     public void setDisabled(final boolean disabled) {
362         super.setDisabled(disabled);
363     }
364 
365     /**
366      * {@inheritDoc}
367      */
368     @JsxGetter
369     @Override
370     public HTMLFormElement getForm() {
371         return super.getForm();
372     }
373 
374     /**
375      * Checks whether the element has any constraints and whether it satisfies them.
376      * @return if the element is valid
377      */
378     @JsxFunction
379     public boolean checkValidity() {
380         return getDomNodeOrDie().isValid();
381     }
382 
383     /**
384      * @return a ValidityState with the validity states that this element is in.
385      */
386     @JsxGetter
387     public ValidityState getValidity() {
388         final ValidityState validityState = new ValidityState();
389         validityState.setPrototype(getPrototype(validityState.getClass()));
390         validityState.setParentScope(getParentScope());
391         validityState.setDomNode(getDomNodeOrDie());
392         return validityState;
393     }
394 
395     /**
396      * @return whether the element is a candidate for constraint validation
397      */
398     @JsxGetter
399     public boolean getWillValidate() {
400         return getDomNodeOrDie().willValidate();
401     }
402 
403     /**
404      * Sets the custom validity message for the element to the specified message.
405      * @param message the new message
406      */
407     @JsxFunction
408     public void setCustomValidity(final String message) {
409         getDomNodeOrDie().setCustomValidity(message);
410     }
411 
412     /**
413      * @return the Iterator symbol
414      */
415     @JsxSymbol
416     public Scriptable iterator() {
417         return getOptions().iterator();
418     }
419 }