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 (e.g. IE6 and Mozilla 1.7)
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          // default value for both IE6 and Mozilla 1.7 even if spec says it is unspecified
65          super(qualifiedName, page, addValueIfNeeded(page, attributes));
66  
67          // fix the default value in case we have set it
68          if (getAttribute("value") == DEFAULT_VALUE) {
69              setDefaultValue(ATTRIBUTE_NOT_DEFINED, false);
70          }
71  
72          defaultCheckedState_ = hasAttribute("checked");
73          checkedState_ = defaultCheckedState_;
74      }
75  
76      /**
77       * Add missing attribute if needed by fixing attribute map rather to add it afterwards as this second option
78       * triggers the instantiation of the script object at a time where the DOM node has not yet been added to its
79       * parent.
80       */
81      private static Map<String, DomAttr> addValueIfNeeded(final SgmlPage page,
82              final Map<String, DomAttr> attributes) {
83  
84          for (final String key : attributes.keySet()) {
85              if ("value".equalsIgnoreCase(key)) {
86                  return attributes; // value attribute was specified
87              }
88          }
89  
90          // value attribute was not specified, add it
91          final DomAttr newAttr = new DomAttr(page, null, "value", DEFAULT_VALUE, true);
92          attributes.put("value", newAttr);
93  
94          return attributes;
95      }
96  
97      /**
98       * {@inheritDoc}
99       *
100      * @see SubmittableElement#reset()
101      */
102     @Override
103     public void reset() {
104         setChecked(defaultCheckedState_);
105     }
106 
107     /**
108      * Returns {@code true} if this element is currently selected.
109      * @return {@code true} if this element is currently selected
110      */
111     @Override
112     public boolean isChecked() {
113         return checkedState_;
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
120     public Page setChecked(final boolean isChecked) {
121         checkedState_ = isChecked;
122 
123         return executeOnChangeHandlerIfAppropriate(this);
124     }
125 
126     /**
127      * A checkbox does not have a textual representation,
128      * but we invent one for it because it is useful for testing.
129      * @return "checked" or "unchecked" according to the radio state
130      */
131     // we need to preserve this method as it is there since many versions with the above documentation.
132     @Override
133     public String asText() {
134         return super.asText();
135     }
136 
137     /**
138      * {@inheritDoc}
139      */
140     @Override
141     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
142         checkedState_ = !isChecked();
143         super.doClickStateUpdate(shiftKey, ctrlKey);
144         return true;
145     }
146 
147     /**
148      * {@inheritDoc}
149      */
150     @Override
151     protected ScriptResult doClickFireClickEvent(final Event event) {
152         if (!hasFeature(EVENT_ONCHANGE_AFTER_ONCLICK)) {
153             executeOnChangeHandlerIfAppropriate(this);
154         }
155 
156         return super.doClickFireClickEvent(event);
157     }
158 
159     /**
160      * {@inheritDoc}
161      */
162     @Override
163     protected void doClickFireChangeEvent() {
164         if (hasFeature(EVENT_ONCHANGE_AFTER_ONCLICK)) {
165             executeOnChangeHandlerIfAppropriate(this);
166         }
167     }
168 
169     /**
170      * Both IE and Mozilla will first update the internal state of checkbox
171      * and then handle "onclick" event.
172      * {@inheritDoc}
173      */
174     @Override
175     protected boolean isStateUpdateFirst() {
176         return true;
177     }
178 
179     /**
180      * {@inheritDoc}
181      */
182     @Override
183     protected void preventDefault() {
184         checkedState_ = !checkedState_;
185     }
186 
187     /**
188      * {@inheritDoc} Also sets the value to the new default value.
189      * @see SubmittableElement#setDefaultValue(String)
190      */
191     @Override
192     public void setDefaultValue(final String defaultValue) {
193         super.setDefaultValue(defaultValue);
194         setValueAttribute(defaultValue);
195     }
196 
197     /**
198      * {@inheritDoc}
199      * @see SubmittableElement#setDefaultChecked(boolean)
200      */
201     @Override
202     public void setDefaultChecked(final boolean defaultChecked) {
203         defaultCheckedState_ = defaultChecked;
204         setChecked(defaultChecked);
205     }
206 
207     /**
208      * {@inheritDoc}
209      * @see SubmittableElement#isDefaultChecked()
210      */
211     @Override
212     public boolean isDefaultChecked() {
213         return defaultCheckedState_;
214     }
215 
216     @Override
217     Object getInternalValue() {
218         return isChecked();
219     }
220 
221     @Override
222     void handleFocusLostValueChanged() {
223     }
224 
225     /**
226      * {@inheritDoc}
227      */
228     @Override
229     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
230             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
231         if ("value".equals(qualifiedName)) {
232             setDefaultValue(attributeValue, false);
233         }
234         if ("checked".equals(qualifiedName)) {
235             checkedState_ = true;
236         }
237         super.setAttributeNS(namespaceURI, qualifiedName, attributeValue, notifyAttributeChangeListeners,
238                 notifyMutationObservers);
239     }
240 
241     /**
242      * {@inheritDoc}
243      */
244     @Override
245     protected boolean propagateClickStateUpdateToParent() {
246         return !hasFeature(HTMLINPUT_CHECKBOX_DOES_NOT_CLICK_SURROUNDING_ANCHOR)
247                 && super.propagateClickStateUpdateToParent();
248     }
249 }