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