View Javadoc
1   /*
2    * Copyright (c) 2002-2026 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.html;
16  
17  import java.io.IOException;
18  import java.io.PrintWriter;
19  import java.util.Map;
20  
21  import org.htmlunit.Page;
22  import org.htmlunit.SgmlPage;
23  import org.htmlunit.html.serializer.HtmlSerializerNormalizedText;
24  import org.htmlunit.javascript.host.event.Event;
25  import org.htmlunit.javascript.host.event.MouseEvent;
26  
27  /**
28   * Wrapper for the HTML element "option".
29   *
30   * @author Mike Bowler
31   * @author David K. Taylor
32   * @author Christian Sell
33   * @author David D. Kilzer
34   * @author Marc Guillemot
35   * @author Ahmed Ashour
36   * @author Daniel Gredler
37   * @author Ronald Brill
38   * @author Frank Danek
39   */
40  public class HtmlOption extends HtmlElement implements DisabledElement {
41  
42      /** The HTML tag represented by this element. */
43      public static final String TAG_NAME = "option";
44  
45      private boolean selected_;
46  
47      /**
48       * Creates an instance.
49       *
50       * @param qualifiedName the qualified name of the element type to instantiate
51       * @param page the page that contains this element
52       * @param attributes the initial attributes
53       */
54      HtmlOption(final String qualifiedName, final SgmlPage page,
55              final Map<String, DomAttr> attributes) {
56          super(qualifiedName, page, attributes);
57          reset();
58      }
59  
60      /**
61       * Returns {@code true} if this option is currently selected.
62       * @return {@code true} if this option is currently selected
63       */
64      public boolean isSelected() {
65          return selected_;
66      }
67  
68      /**
69       * Sets the selected state of this option. This will possibly also change the
70       * selected properties of sibling option elements.
71       *
72       * @param selected true if this option should be selected
73       * @return the page that occupies this window after this change is made (may or
74       *         may not be the same as the original page)
75       */
76      public Page setSelected(final boolean selected) {
77          setSelected(selected, true, false, false, false);
78          return getPage();
79      }
80  
81      /**
82       * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
83       *
84       * Sets the selected state of this option. This will possibly also change the
85       * selected properties of sibling option elements.
86       *
87       * @param selected true if this option should be selected
88       */
89      public void setSelectedFromJavaScript(final boolean selected) {
90          setSelected(selected, false, false, true, false);
91      }
92  
93      /**
94       * Sets the selected state of this option. This will possibly also change the
95       * selected properties of sibling option elements.
96       *
97       * @param selected true if this option should be selected
98       * @param invokeOnFocus whether to set focus or not.
99       * @param isClick is mouse clicked
100      * @param shiftKey {@code true} if SHIFT is pressed
101      * @param ctrlKey {@code true} if CTRL is pressed
102      */
103     private void setSelected(final boolean selected, final boolean invokeOnFocus, final boolean isClick,
104             final boolean shiftKey, final boolean ctrlKey) {
105         if (selected == isSelected()) {
106             return;
107         }
108         final HtmlSelect select = getEnclosingSelect();
109         if (select != null) {
110             select.setSelectedAttribute(this, selected, invokeOnFocus, shiftKey, ctrlKey, isClick);
111             return;
112         }
113         // for instance from JS for an option created by document.createElement('option')
114         // and not yet added to a select
115         setSelectedInternal(selected);
116     }
117 
118     /**
119      * {@inheritDoc}
120      */
121     @Override
122     public void insertBefore(final DomNode newNode) {
123         super.insertBefore(newNode);
124         if (newNode instanceof HtmlOption option) {
125             if (option.isSelected()) {
126                 getEnclosingSelect().setSelectedAttribute(option, true);
127             }
128         }
129     }
130 
131     /**
132      * Gets the enclosing select of this option.
133      * @return {@code null} if no select is found (for instance malformed HTML)
134      */
135     public HtmlSelect getEnclosingSelect() {
136         return (HtmlSelect) getEnclosingElement(HtmlSelect.TAG_NAME);
137     }
138 
139     /**
140      * Resets the option to its original selected state.
141      */
142     public void reset() {
143         setSelectedInternal(hasAttribute("selected"));
144     }
145 
146     /**
147      * Returns the value of the attribute {@code selected}. Refer to the
148      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
149      * documentation for details on the use of this attribute.
150      *
151      * @return the value of the attribute {@code selected}
152      *         or an empty string if that attribute isn't defined.
153      */
154     public final String getSelectedAttribute() {
155         return getAttributeDirect("selected");
156     }
157 
158     /**
159      * Returns whether this Option is selected by default.
160      * That is whether the "selected"
161      * attribute exists when the Option is constructed. This also determines
162      * the value of getSelectedAttribute() after a reset() on the form.
163      * @return whether the option is selected by default
164      */
165     public final boolean isDefaultSelected() {
166         return hasAttribute("selected");
167     }
168 
169     /**
170      * {@inheritDoc}
171      */
172     @Override
173     public final String getDisabledAttribute() {
174         return getAttributeDirect(ATTRIBUTE_DISABLED);
175     }
176 
177     /**
178      * Returns the value of the attribute {@code label}. Refer to the
179      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
180      * documentation for details on the use of this attribute.
181      *
182      * @return the value of the attribute {@code label} or an empty string if that attribute isn't defined
183      */
184     public final String getLabelAttribute() {
185         return getAttributeDirect("label");
186     }
187 
188     /**
189      * Sets the value of the attribute {@code label}. Refer to the
190      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
191      * documentation for details on the use of this attribute.
192      *
193      * @param newLabel the value of the attribute {@code label}
194      */
195     public final void setLabelAttribute(final String newLabel) {
196         setAttribute("label", newLabel);
197     }
198 
199     /**
200      * Returns the value of the attribute {@code value}. Refer to the
201      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
202      * documentation for details on the use of this attribute.
203      * @see <a href="http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION">
204      *     initial value if value attribute is not set</a>
205      * @return the value of the attribute {@code value}
206      */
207     public final String getValueAttribute() {
208         String value = getAttributeDirect(VALUE_ATTRIBUTE);
209         if (ATTRIBUTE_NOT_DEFINED == value) {
210             value = getText();
211         }
212         return value;
213     }
214 
215     /**
216      * Sets the value of the attribute {@code value}. Refer to the
217      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
218      * documentation for details on the use of this attribute.
219      *
220      * @param newValue the value of the attribute {@code value}
221      */
222     public final void setValueAttribute(final String newValue) {
223         setAttribute(VALUE_ATTRIBUTE, newValue);
224     }
225 
226     /**
227      * Selects the option if it's not already selected.
228      * {@inheritDoc}
229      */
230     @Override
231     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
232         boolean changed = false;
233         if (!isSelected()) {
234             setSelected(true, true, true, shiftKey, ctrlKey);
235             changed = true;
236         }
237         else if (getEnclosingSelect().isMultipleSelectEnabled()) {
238             if (ctrlKey) {
239                 setSelected(false, true, true, shiftKey, ctrlKey);
240                 changed = true;
241             }
242             else {
243                 getEnclosingSelect().setOnlySelected(this, true);
244             }
245         }
246         super.doClickStateUpdate(shiftKey, ctrlKey);
247         return changed;
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
254     protected boolean isStateUpdateFirst() {
255         return true;
256     }
257 
258     /**
259      * {@inheritDoc}
260      */
261     @Override
262     protected void printOpeningTagContentAsXml(final PrintWriter printWriter) {
263         super.printOpeningTagContentAsXml(printWriter);
264         if (selected_ && getAttributeDirect("selected") == ATTRIBUTE_NOT_DEFINED) {
265             printWriter.print(" selected=\"selected\"");
266         }
267     }
268 
269     /**
270      * For internal use only.
271      * Sets/remove the selected attribute to reflect the select state
272      * @param selected the selected status
273      */
274     void setSelectedInternal(final boolean selected) {
275         selected_ = selected;
276     }
277 
278     /**
279      * Sets the text for this HtmlOption.
280      * @param text the text
281      */
282     public void setText(final String text) {
283         if (text == null || text.isEmpty()) {
284             removeAllChildren();
285         }
286         else {
287             final DomNode child = getFirstChild();
288             if (child == null) {
289                 appendChild(new DomText(getPage(), text));
290             }
291             else {
292                 child.setNodeValue(text);
293             }
294         }
295     }
296 
297     /**
298      * Gets the text.
299      * @return the text of this option.
300      */
301     public String getText() {
302         final HtmlSerializerNormalizedText ser = new HtmlSerializerNormalizedText();
303         ser.setIgnoreMaskedElements(false);
304         return ser.asText(this);
305     }
306 
307     /**
308      * {@inheritDoc}
309      */
310     @Override
311     public Page mouseOver(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
312         // to move the mouse over the oution we will touch the select (border)
313         // depending on your mous speed and the browser this event is triggered or not
314         getEnclosingSelect().mouseOver(shiftKey, ctrlKey, altKey, button);
315 
316         return super.mouseOver(shiftKey, ctrlKey, altKey, button);
317     }
318 
319     /**
320      * {@inheritDoc}
321      */
322     @Override
323     public DisplayStyle getDefaultStyleDisplay() {
324         return DisplayStyle.BLOCK;
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
330     @Override
331     public boolean handles(final Event event) {
332         if (MouseEvent.TYPE_MOUSE_OVER.equals(event.getType())) {
333             return true;
334         }
335         return super.handles(event);
336     }
337 
338     /**
339      * {@inheritDoc}
340      */
341     @Override
342     protected void basicRemove() {
343         final DomNode parent = getParentNode();
344         super.basicRemove();
345 
346         if (parent != null && isSelected()) {
347             // update selection and size if needed
348             parent.onAllChildrenAddedToPage(false);
349         }
350     }
351 }