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 * @return the normalized type value (submit|reset|button).
253 */
254 public String getType() {
255 final String type = getTypeAttribute();
256 if (TYPE_RESET.equalsIgnoreCase(type)) {
257 return TYPE_RESET;
258 }
259 if (TYPE_BUTTON.equalsIgnoreCase(type)) {
260 return TYPE_BUTTON;
261 }
262 return TYPE_SUBMIT;
263 }
264
265 /**
266 * Returns the value of the attribute {@code disabled}. Refer to the
267 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
268 * documentation for details on the use of this attribute.
269 *
270 * @return the value of the attribute {@code disabled} or an empty string if that attribute isn't defined
271 */
272 @Override
273 public final String getDisabledAttribute() {
274 return getAttributeDirect(ATTRIBUTE_DISABLED);
275 }
276
277 /**
278 * Returns the value of the attribute {@code tabindex}. Refer to the
279 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
280 * documentation for details on the use of this attribute.
281 *
282 * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
283 */
284 public final String getTabIndexAttribute() {
285 return getAttributeDirect("tabindex");
286 }
287
288 /**
289 * Returns the value of the attribute {@code accesskey}. Refer to the
290 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
291 * documentation for details on the use of this attribute.
292 *
293 * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
294 */
295 public final String getAccessKeyAttribute() {
296 return getAttributeDirect("accesskey");
297 }
298
299 /**
300 * Returns the value of the attribute {@code onfocus}. Refer to the
301 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
302 * documentation for details on the use of this attribute.
303 *
304 * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
305 */
306 public final String getOnFocusAttribute() {
307 return getAttributeDirect("onfocus");
308 }
309
310 /**
311 * Returns the value of the attribute {@code onblur}. Refer to the
312 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
313 * documentation for details on the use of this attribute.
314 *
315 * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
316 */
317 public final String getOnBlurAttribute() {
318 return getAttributeDirect("onblur");
319 }
320
321 /**
322 * {@inheritDoc}
323 */
324 @Override
325 public DisplayStyle getDefaultStyleDisplay() {
326 return DisplayStyle.INLINE_BLOCK;
327 }
328
329 /**
330 * {@inheritDoc}
331 * @return {@code true} to make generated XML readable as HTML.
332 */
333 @Override
334 protected boolean isEmptyXmlTagExpanded() {
335 return true;
336 }
337
338 /**
339 * {@inheritDoc}
340 */
341 @Override
342 public boolean isValid() {
343 if (TYPE_RESET.equals(getType())) {
344 return true;
345 }
346
347 return super.isValid() && !isCustomErrorValidityState();
348 }
349
350 /**
351 * {@inheritDoc}
352 */
353 @Override
354 public boolean willValidate() {
355 if (TYPE_RESET.equals(getType()) || TYPE_BUTTON.equals(getType())) {
356 return false;
357 }
358
359 return !isDisabled();
360 }
361
362 /**
363 * {@inheritDoc}
364 */
365 @Override
366 public void setCustomValidity(final String message) {
367 customValidity_ = message;
368 }
369
370 /**
371 * {@inheritDoc}
372 */
373 @Override
374 public boolean isCustomErrorValidityState() {
375 return !StringUtils.isEmptyOrNull(customValidity_);
376 }
377
378 @Override
379 public boolean isValidValidityState() {
380 return !isCustomErrorValidityState();
381 }
382
383 /**
384 * @return the value of the attribute {@code formnovalidate} or an empty string if that attribute isn't defined
385 */
386 public final boolean isFormNoValidate() {
387 return hasAttribute(ATTRIBUTE_FORMNOVALIDATE);
388 }
389
390 /**
391 * Sets the value of the attribute {@code formnovalidate}.
392 *
393 * @param noValidate the value of the attribute {@code formnovalidate}
394 */
395 public final void setFormNoValidate(final boolean noValidate) {
396 if (noValidate) {
397 setAttribute(ATTRIBUTE_FORMNOVALIDATE, ATTRIBUTE_FORMNOVALIDATE);
398 }
399 else {
400 removeAttribute(ATTRIBUTE_FORMNOVALIDATE);
401 }
402 }
403 }