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 static org.htmlunit.util.StringUtils.toRootLowerCase;
18  
19  import java.io.IOException;
20  import java.util.Map;
21  
22  import org.htmlunit.Page;
23  import org.htmlunit.SgmlPage;
24  import org.htmlunit.javascript.host.event.KeyboardEvent;
25  
26  /**
27   * Wrapper for the HTML element "input".
28   *
29   * @author Mike Bowler
30   * @author David K. Taylor
31   * @author Jun Chen
32   * @author Christian Sell
33   * @author Marc Guillemot
34   * @author Mike Bresnahan
35   * @author Daniel Gredler
36   * @author Ahmed Ashour
37   * @author Ronald Brill
38   * @author Frank Danek
39   */
40  public class HtmlCheckBoxInput extends HtmlInput implements LabelableElement {
41  
42      /**
43       * Value to use if no specified <code>value</code> attribute.
44       */
45      private static final String DEFAULT_VALUE = "on";
46  
47      private boolean defaultCheckedState_;
48      private boolean checkedState_;
49  
50      /**
51       * Creates an instance.
52       * If no value is specified, it is set to "on" as browsers do
53       * even if spec says that it is not allowed
54       * (<a href="http://www.w3.org/TR/REC-html40/interact/forms.html#adef-value-INPUT">W3C</a>).
55       *
56       * @param qualifiedName the qualified name of the element type to instantiate
57       * @param page the page that contains this element
58       * @param attributes the initial attributes
59       */
60      HtmlCheckBoxInput(final String qualifiedName, final SgmlPage page,
61              final Map<String, DomAttr> attributes) {
62          super(qualifiedName, page, attributes);
63  
64          if (getAttributeDirect(VALUE_ATTRIBUTE) == ATTRIBUTE_NOT_DEFINED) {
65              setRawValue(DEFAULT_VALUE);
66          }
67  
68          defaultCheckedState_ = hasAttribute(ATTRIBUTE_CHECKED);
69          checkedState_ = defaultCheckedState_;
70      }
71  
72      /**
73       * Returns {@code true} if this element is currently selected.
74       * @return {@code true} if this element is currently selected
75       */
76      @Override
77      public boolean isChecked() {
78          return checkedState_;
79      }
80  
81      /**
82       * {@inheritDoc}
83       * @see SubmittableElement#reset()
84       */
85      @Override
86      public void reset() {
87          setChecked(defaultCheckedState_);
88      }
89  
90      /**
91       * {@inheritDoc}
92       */
93      @Override
94      public Page setChecked(final boolean isChecked) {
95          checkedState_ = isChecked;
96          return executeOnChangeHandlerIfAppropriate(this);
97      }
98  
99      /**
100      * {@inheritDoc}
101      */
102     @Override
103     protected void doType(final int keyCode, final boolean lastType) throws IOException {
104         if (keyCode == KeyboardEvent.DOM_VK_SPACE) {
105             click();
106             return;
107         }
108 
109         super.doType(keyCode, lastType);
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
117         checkedState_ = !isChecked();
118         super.doClickStateUpdate(shiftKey, ctrlKey);
119         return true;
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
126     protected void doClickFireChangeEvent() {
127         executeOnChangeHandlerIfAppropriate(this);
128     }
129 
130     /**
131      * First update the internal state of checkbox and then handle "onclick" event.
132      * {@inheritDoc}
133      */
134     @Override
135     protected boolean isStateUpdateFirst() {
136         return true;
137     }
138 
139     /**
140      * {@inheritDoc}
141      */
142     @Override
143     protected void preventDefault() {
144         checkedState_ = !checkedState_;
145     }
146 
147     /**
148      * {@inheritDoc}
149      * Also sets the value to the new default value.
150      * @see SubmittableElement#setDefaultValue(String)
151      */
152     @Override
153     public void setDefaultValue(final String defaultValue) {
154         super.setDefaultValue(defaultValue);
155         setValue(defaultValue);
156     }
157 
158     /**
159      * {@inheritDoc}
160      * Also sets the default value.
161      */
162     @Override
163     public void setValue(final String newValue) {
164         super.setValue(newValue);
165         super.setDefaultValue(newValue);
166     }
167 
168     /**
169      * {@inheritDoc}
170      * @see SubmittableElement#setDefaultChecked(boolean)
171      */
172     @Override
173     public void setDefaultChecked(final boolean defaultChecked) {
174         defaultCheckedState_ = defaultChecked;
175         setChecked(isDefaultChecked());
176     }
177 
178     /**
179      * {@inheritDoc}
180      * @see SubmittableElement#isDefaultChecked()
181      */
182     @Override
183     public boolean isDefaultChecked() {
184         return defaultCheckedState_;
185     }
186 
187     @Override
188     protected Object getInternalValue() {
189         return isChecked();
190     }
191 
192     @Override
193     void handleFocusLostValueChanged() {
194         // ignore
195     }
196 
197     /**
198      * {@inheritDoc}
199      */
200     @Override
201     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
202             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
203         final String qualifiedNameLC = toRootLowerCase(qualifiedName);
204 
205         if (VALUE_ATTRIBUTE.equals(qualifiedNameLC)) {
206             super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
207                     notifyMutationObservers);
208             setRawValue(attributeValue);
209             return;
210         }
211 
212         if (ATTRIBUTE_CHECKED.equals(qualifiedNameLC)) {
213             checkedState_ = true;
214         }
215         super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
216                 notifyMutationObservers);
217     }
218 
219     /**
220      * {@inheritDoc}
221      */
222     @Override
223     protected boolean propagateClickStateUpdateToParent() {
224         return false;
225     }
226 
227     @Override
228     public boolean isValueMissingValidityState() {
229         return hasAttribute(ATTRIBUTE_REQUIRED) && !isChecked();
230     }
231 }