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.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_ONCHANGE_AFTER_ONCLICK;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLINPUT_CHECKBOX_DOES_NOT_CLICK_SURROUNDING_ANCHOR;
19  
20  import java.io.IOException;
21  import java.util.Map;
22  
23  import com.gargoylesoftware.htmlunit.Page;
24  import com.gargoylesoftware.htmlunit.ScriptResult;
25  import com.gargoylesoftware.htmlunit.SgmlPage;
26  import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
27  
28  /**
29   * Wrapper for the HTML element "input".
30   *
31   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
32   * @author David K. Taylor
33   * @author <a href="mailto:chen_jun@users.sourceforge.net">Jun Chen</a>
34   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
35   * @author Marc Guillemot
36   * @author Mike Bresnahan
37   * @author Daniel Gredler
38   * @author Ahmed Ashour
39   * @author Ronald Brill
40   * @author Frank Danek
41   */
42  public class HtmlCheckBoxInput extends HtmlInput {
43  
44      /**
45       * Value to use if no specified <tt>value</tt> attribute.
46       */
47      private static final String DEFAULT_VALUE = "on";
48  
49      private boolean defaultCheckedState_;
50      private boolean checkedState_;
51  
52      /**
53       * Creates an instance.
54       * If no value is specified, it is set to "on" as browsers do
55       * even if spec says that it is not allowed
56       * (<a href="http://www.w3.org/TR/REC-html40/interact/forms.html#adef-value-INPUT">W3C</a>).
57       *
58       * @param qualifiedName the qualified name of the element type to instantiate
59       * @param page the page that contains this element
60       * @param attributes the initial attributes
61       */
62      HtmlCheckBoxInput(final String qualifiedName, final SgmlPage page,
63              final Map<String, DomAttr> attributes) {
64          super(qualifiedName, page, addValueIfNeeded(page, attributes));
65  
66          // fix the default value in case we have set it
67          if (getAttribute("value") == DEFAULT_VALUE) {
68              setDefaultValue(ATTRIBUTE_NOT_DEFINED, false);
69          }
70  
71          defaultCheckedState_ = hasAttribute("checked");
72          checkedState_ = defaultCheckedState_;
73      }
74  
75      /**
76       * Add missing attribute if needed by fixing attribute map rather to add it afterwards as this second option
77       * triggers the instantiation of the script object at a time where the DOM node has not yet been added to its
78       * parent.
79       */
80      private static Map<String, DomAttr> addValueIfNeeded(final SgmlPage page,
81              final Map<String, DomAttr> attributes) {
82  
83          for (final String key : attributes.keySet()) {
84              if ("value".equalsIgnoreCase(key)) {
85                  return attributes; // value attribute was specified
86              }
87          }
88  
89          // value attribute was not specified, add it
90          final DomAttr newAttr = new DomAttr(page, null, "value", DEFAULT_VALUE, true);
91          attributes.put("value", newAttr);
92  
93          return attributes;
94      }
95  
96      /**
97       * {@inheritDoc}
98       *
99       * @see SubmittableElement#reset()
100      */
101     @Override
102     public void reset() {
103         setChecked(defaultCheckedState_);
104     }
105 
106     /**
107      * Returns {@code true} if this element is currently selected.
108      * @return {@code true} if this element is currently selected
109      */
110     @Override
111     public boolean isChecked() {
112         return checkedState_;
113     }
114 
115     /**
116      * {@inheritDoc}
117      */
118     @Override
119     public Page setChecked(final boolean isChecked) {
120         checkedState_ = isChecked;
121 
122         return executeOnChangeHandlerIfAppropriate(this);
123     }
124 
125     /**
126      * A checkbox does not have a textual representation,
127      * but we invent one for it because it is useful for testing.
128      * @return "checked" or "unchecked" according to the radio state
129      */
130     // we need to preserve this method as it is there since many versions with the above documentation.
131     @Override
132     public String asText() {
133         return super.asText();
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
141         checkedState_ = !isChecked();
142         super.doClickStateUpdate(shiftKey, ctrlKey);
143         return true;
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     @Override
150     protected ScriptResult doClickFireClickEvent(final Event event) {
151         if (!hasFeature(EVENT_ONCHANGE_AFTER_ONCLICK)) {
152             executeOnChangeHandlerIfAppropriate(this);
153         }
154 
155         return super.doClickFireClickEvent(event);
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
162     protected void doClickFireChangeEvent() {
163         if (hasFeature(EVENT_ONCHANGE_AFTER_ONCLICK)) {
164             executeOnChangeHandlerIfAppropriate(this);
165         }
166     }
167 
168     /**
169      * First update the internal state of checkbox and then handle "onclick" event.
170      * {@inheritDoc}
171      */
172     @Override
173     protected boolean isStateUpdateFirst() {
174         return true;
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     protected void preventDefault() {
182         checkedState_ = !checkedState_;
183     }
184 
185     /**
186      * {@inheritDoc} Also sets the value to the new default value.
187      * @see SubmittableElement#setDefaultValue(String)
188      */
189     @Override
190     public void setDefaultValue(final String defaultValue) {
191         super.setDefaultValue(defaultValue);
192         setValueAttribute(defaultValue);
193     }
194 
195     /**
196      * {@inheritDoc}
197      * @see SubmittableElement#setDefaultChecked(boolean)
198      */
199     @Override
200     public void setDefaultChecked(final boolean defaultChecked) {
201         defaultCheckedState_ = defaultChecked;
202         setChecked(defaultChecked);
203     }
204 
205     /**
206      * {@inheritDoc}
207      * @see SubmittableElement#isDefaultChecked()
208      */
209     @Override
210     public boolean isDefaultChecked() {
211         return defaultCheckedState_;
212     }
213 
214     @Override
215     Object getInternalValue() {
216         return isChecked();
217     }
218 
219     @Override
220     void handleFocusLostValueChanged() {
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
228             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
229         if ("value".equals(qualifiedName)) {
230             setDefaultValue(attributeValue, false);
231         }
232         if ("checked".equals(qualifiedName)) {
233             checkedState_ = true;
234         }
235         super.setAttributeNS(namespaceURI, qualifiedName, attributeValue, notifyAttributeChangeListeners,
236                 notifyMutationObservers);
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     @Override
243     protected boolean propagateClickStateUpdateToParent() {
244         return !hasFeature(HTMLINPUT_CHECKBOX_DOES_NOT_CLICK_SURROUNDING_ANCHOR)
245                 && super.propagateClickStateUpdateToParent();
246     }
247 }