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 }