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.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.HTMLBUTTON_WILL_VALIDATE_IGNORES_READONLY;
18  import static org.htmlunit.html.HtmlForm.ATTRIBUTE_FORMNOVALIDATE;
19  
20  import java.io.IOException;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Map;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.htmlunit.SgmlPage;
30  import org.htmlunit.javascript.host.event.Event;
31  import org.htmlunit.javascript.host.event.MouseEvent;
32  import org.htmlunit.util.NameValuePair;
33  import org.w3c.dom.Node;
34  
35  /**
36   * Wrapper for the HTML element "button".
37   *
38   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
39   * @author David K. Taylor
40   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
41   * @author David D. Kilzer
42   * @author Daniel Gredler
43   * @author Ahmed Ashour
44   * @author Dmitri Zoubkov
45   * @author Ronald Brill
46   * @author Frank Danek
47   * @author Sven Strickroth
48   */
49  public class HtmlButton extends HtmlElement implements DisabledElement, SubmittableElement,
50                  LabelableElement, FormFieldWithNameHistory, ValidatableElement {
51  
52      private static final Log LOG = LogFactory.getLog(HtmlButton.class);
53  
54      /** The HTML tag represented by this element. */
55      public static final String TAG_NAME = "button";
56  
57      private static final String TYPE_SUBMIT = "submit";
58      private static final String TYPE_RESET = "reset";
59      private static final String TYPE_BUTTON = "button";
60  
61      private final String originalName_;
62      private Collection<String> newNames_ = Collections.emptySet();
63      private String customValidity_;
64  
65      /**
66       * Creates a new instance.
67       *
68       * @param qualifiedName the qualified name of the element type to instantiate
69       * @param page the page that contains this element
70       * @param attributes the initial attributes
71       */
72      HtmlButton(final String qualifiedName, final SgmlPage page,
73              final Map<String, DomAttr> attributes) {
74          super(qualifiedName, page, attributes);
75          originalName_ = getNameAttribute();
76      }
77  
78      /**
79       * Sets the content of the {@code value} attribute.
80       *
81       * @param newValue the new content
82       */
83      public void setValueAttribute(final String newValue) {
84          setAttribute(VALUE_ATTRIBUTE, newValue);
85      }
86  
87      /**
88       * {@inheritDoc}
89       */
90      @Override
91      protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
92          if (!isDisabled()) {
93              final HtmlForm form = getEnclosingForm();
94              if (form != null) {
95                  final String type = getType();
96                  if (TYPE_BUTTON.equals(type)) {
97                      return false;
98                  }
99  
100                 if (TYPE_RESET.equals(type)) {
101                     form.reset();
102                     return false;
103                 }
104 
105                 form.submit(this);
106                 return false;
107             }
108         }
109 
110         super.doClickStateUpdate(shiftKey, ctrlKey);
111         return false;
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     @Override
118     public final boolean isDisabled() {
119         if (hasAttribute(ATTRIBUTE_DISABLED)) {
120             return true;
121         }
122 
123         Node node = getParentNode();
124         while (node != null) {
125             if (node instanceof DisabledElement
126                     && ((DisabledElement) node).isDisabled()) {
127                 return true;
128             }
129             node = node.getParentNode();
130         }
131 
132         return false;
133     }
134 
135     /**
136      * Returns {@code true} if this element is read only.
137      * @return {@code true} if this element is read only
138      */
139     public boolean isReadOnly() {
140         return hasAttribute("readOnly");
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
147     public NameValuePair[] getSubmitNameValuePairs() {
148         return new NameValuePair[]{new NameValuePair(getNameAttribute(), getValueAttribute())};
149     }
150 
151     /**
152      * {@inheritDoc}
153      *
154      * @see SubmittableElement#reset()
155      */
156     @Override
157     public void reset() {
158         LOG.debug("reset() not implemented for this element");
159     }
160 
161     /**
162      * {@inheritDoc}
163      *
164      * @see SubmittableElement#setDefaultValue(String)
165      */
166     @Override
167     public void setDefaultValue(final String defaultValue) {
168         LOG.debug("setDefaultValue() not implemented for this element");
169     }
170 
171     /**
172      * {@inheritDoc}
173      *
174      * @see SubmittableElement#getDefaultValue()
175      */
176     @Override
177     public String getDefaultValue() {
178         LOG.debug("getDefaultValue() not implemented for this element");
179         return "";
180     }
181 
182     /**
183      * {@inheritDoc}
184      *
185      * This implementation is empty; only checkboxes and radio buttons really care what the
186      * default checked value is.
187      *
188      * @see SubmittableElement#setDefaultChecked(boolean)
189      * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
190      * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
191      */
192     @Override
193     public void setDefaultChecked(final boolean defaultChecked) {
194         // Empty.
195     }
196 
197     /**
198      * {@inheritDoc}
199      *
200      * This implementation returns {@code false}; only checkboxes and radio buttons really care what
201      * the default checked value is.
202      *
203      * @see SubmittableElement#isDefaultChecked()
204      * @see HtmlRadioButtonInput#isDefaultChecked()
205      * @see HtmlCheckBoxInput#isDefaultChecked()
206      */
207     @Override
208     public boolean isDefaultChecked() {
209         return false;
210     }
211 
212     /**
213      * {@inheritDoc}
214      */
215     @Override
216     public boolean handles(final Event event) {
217         if (event instanceof MouseEvent) {
218             return true;
219         }
220 
221         return super.handles(event);
222     }
223 
224     /**
225      * Returns the value of the attribute {@code name}. Refer to the
226      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
227      * documentation for details on the use of this attribute.
228      *
229      * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
230      */
231     public final String getNameAttribute() {
232         return getAttributeDirect(NAME_ATTRIBUTE);
233     }
234 
235     /**
236      * Returns the value of the attribute {@code value}. Refer to the
237      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
238      * documentation for details on the use of this attribute.
239      *
240      * @return the value of the attribute {@code value} or an empty string if that attribute isn't defined
241      */
242     public final String getValueAttribute() {
243         return getAttributeDirect(VALUE_ATTRIBUTE);
244     }
245 
246     /**
247      * Returns the value of the attribute {@code type}. Refer to the
248      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
249      * documentation for details on the use of this attribute.
250      *
251      * @return the value of the attribute {@code type} or the default value if that attribute isn't defined
252      */
253     public final String getTypeAttribute() {
254         return getAttribute(TYPE_ATTRIBUTE);
255     }
256 
257     /**
258      * @return the normalized type value (submit|reset|button).
259      */
260     public String getType() {
261         final String type = getTypeAttribute();
262         if (TYPE_RESET.equalsIgnoreCase(type)) {
263             return TYPE_RESET;
264         }
265         if (TYPE_BUTTON.equalsIgnoreCase(type)) {
266             return TYPE_BUTTON;
267         }
268         return TYPE_SUBMIT;
269     }
270 
271     /**
272      * Returns the value of the attribute {@code disabled}. Refer to the
273      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
274      * documentation for details on the use of this attribute.
275      *
276      * @return the value of the attribute {@code disabled} or an empty string if that attribute isn't defined
277      */
278     @Override
279     public final String getDisabledAttribute() {
280         return getAttributeDirect(ATTRIBUTE_DISABLED);
281     }
282 
283     /**
284      * Returns the value of the attribute {@code tabindex}. Refer to the
285      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
286      * documentation for details on the use of this attribute.
287      *
288      * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
289      */
290     public final String getTabIndexAttribute() {
291         return getAttributeDirect("tabindex");
292     }
293 
294     /**
295      * Returns the value of the attribute {@code accesskey}. Refer to the
296      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
297      * documentation for details on the use of this attribute.
298      *
299      * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
300      */
301     public final String getAccessKeyAttribute() {
302         return getAttributeDirect("accesskey");
303     }
304 
305     /**
306      * Returns the value of the attribute {@code onfocus}. Refer to the
307      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
308      * documentation for details on the use of this attribute.
309      *
310      * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
311      */
312     public final String getOnFocusAttribute() {
313         return getAttributeDirect("onfocus");
314     }
315 
316     /**
317      * Returns the value of the attribute {@code onblur}. Refer to the
318      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
319      * documentation for details on the use of this attribute.
320      *
321      * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
322      */
323     public final String getOnBlurAttribute() {
324         return getAttributeDirect("onblur");
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
330     @Override
331     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
332             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
333         final String qualifiedNameLC = org.htmlunit.util.StringUtils.toRootLowerCase(qualifiedName);
334         if (NAME_ATTRIBUTE.equals(qualifiedNameLC)) {
335             if (newNames_.isEmpty()) {
336                 newNames_ = new HashSet<>();
337             }
338             newNames_.add(attributeValue);
339         }
340         super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
341                 notifyMutationObservers);
342     }
343 
344     /**
345      * {@inheritDoc}
346      */
347     @Override
348     public String getOriginalName() {
349         return originalName_;
350     }
351 
352     /**
353      * {@inheritDoc}
354      */
355     @Override
356     public Collection<String> getNewNames() {
357         return newNames_;
358     }
359 
360     /**
361      * {@inheritDoc}
362      */
363     @Override
364     public DisplayStyle getDefaultStyleDisplay() {
365         return DisplayStyle.INLINE_BLOCK;
366     }
367 
368     /**
369      * {@inheritDoc}
370      * @return {@code true} to make generated XML readable as HTML.
371      */
372     @Override
373     protected boolean isEmptyXmlTagExpanded() {
374         return true;
375     }
376 
377     /**
378      * {@inheritDoc}
379      */
380     @Override
381     public boolean isValid() {
382         if (TYPE_RESET.equals(getType())) {
383             return true;
384         }
385 
386         return super.isValid() && !isCustomErrorValidityState();
387     }
388 
389     /**
390      * {@inheritDoc}
391      */
392     @Override
393     public boolean willValidate() {
394         if (TYPE_RESET.equals(getType()) || TYPE_BUTTON.equals(getType())) {
395             return false;
396         }
397 
398         return !isDisabled()
399                 && (hasFeature(HTMLBUTTON_WILL_VALIDATE_IGNORES_READONLY) || !isReadOnly());
400     }
401 
402     /**
403      * {@inheritDoc}
404      */
405     @Override
406     public void setCustomValidity(final String message) {
407         customValidity_ = message;
408     }
409 
410     /**
411      * {@inheritDoc}
412      */
413     @Override
414     public boolean isCustomErrorValidityState() {
415         return !StringUtils.isEmpty(customValidity_);
416     }
417 
418     @Override
419     public boolean isValidValidityState() {
420         return !isCustomErrorValidityState();
421     }
422 
423     /**
424      * @return the value of the attribute {@code formnovalidate} or an empty string if that attribute isn't defined
425      */
426     public final boolean isFormNoValidate() {
427         return hasAttribute(ATTRIBUTE_FORMNOVALIDATE);
428     }
429 
430     /**
431      * Sets the value of the attribute {@code formnovalidate}.
432      *
433      * @param noValidate the value of the attribute {@code formnovalidate}
434      */
435     public final void setFormNoValidate(final boolean noValidate) {
436         if (noValidate) {
437             setAttribute(ATTRIBUTE_FORMNOVALIDATE, ATTRIBUTE_FORMNOVALIDATE);
438         }
439         else {
440             removeAttribute(ATTRIBUTE_FORMNOVALIDATE);
441         }
442     }
443 }