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 java.io.PrintWriter;
18 import java.util.Map;
19
20 import org.htmlunit.SgmlPage;
21 import org.htmlunit.html.impl.SelectableTextInput;
22 import org.htmlunit.html.impl.SelectableTextSelectionDelegate;
23 import org.htmlunit.javascript.host.event.Event;
24 import org.htmlunit.javascript.host.event.MouseEvent;
25 import org.htmlunit.util.NameValuePair;
26 import org.htmlunit.util.StringUtils;
27 import org.w3c.dom.Node;
28
29 /**
30 * Wrapper for the HTML element "textarea".
31 *
32 * @author Mike Bowler
33 * @author Barnaby Court
34 * @author David K. Taylor
35 * @author Christian Sell
36 * @author David D. Kilzer
37 * @author Marc Guillemot
38 * @author Daniel Gredler
39 * @author Ahmed Ashour
40 * @author Sudhan Moghe
41 * @author Amit Khanna
42 * @author Ronald Brill
43 * @author Frank Danek
44 * @author Lai Quang Duong
45 */
46 public class HtmlTextArea extends HtmlElement implements DisabledElement, SubmittableElement,
47 LabelableElement, SelectableTextInput, ValidatableElement {
48 /** The HTML tag represented by this element. */
49 public static final String TAG_NAME = "textarea";
50
51 private String defaultValue_;
52 private String valueAtFocus_;
53 private String customValidity_;
54
55 private SelectableTextSelectionDelegate selectionDelegate_ = new SelectableTextSelectionDelegate(this);
56 private DoTypeProcessor doTypeProcessor_ = new DoTypeProcessor(this);
57
58 /**
59 * Creates an instance.
60 *
61 * @param qualifiedName the qualified name of the element type to instantiate
62 * @param page the page that contains this element
63 * @param attributes the initial attributes
64 */
65 HtmlTextArea(final String qualifiedName, final SgmlPage page,
66 final Map<String, DomAttr> attributes) {
67 super(qualifiedName, page, attributes);
68 }
69
70 /**
71 * Initializes the default value if necessary. We cannot do it in the constructor
72 * because the child node variable will not have been initialized yet. Must be called
73 * from all methods that use the default value.
74 */
75 private void initDefaultValue() {
76 if (defaultValue_ == null) {
77 defaultValue_ = readValue();
78 }
79 }
80
81 /**
82 * {@inheritDoc}
83 */
84 @Override
85 public boolean handles(final Event event) {
86 if (event instanceof MouseEvent) {
87 return true;
88 }
89
90 return super.handles(event);
91 }
92
93 /**
94 * Returns the value that would be displayed in the text area.
95 *
96 * @return the text
97 */
98 @Override
99 public final String getText() {
100 return readValue();
101 }
102
103 private String readValue() {
104 final StringBuilder builder = new StringBuilder();
105 for (final DomNode node : getChildren()) {
106 if (node instanceof DomText text) {
107 builder.append(text.getData());
108 }
109 }
110 // if content starts with new line, it is ignored (=> for the parser?)
111 if (builder.length() != 0 && builder.charAt(0) == '\n') {
112 builder.deleteCharAt(0);
113 }
114 return builder.toString();
115 }
116
117 /**
118 * Sets the new value of this text area.
119 * <p>
120 * Note that this acts like 'pasting' the text, but to simulate characters entry
121 * you should use {@link #type(String)}.
122 * </p>
123 *
124 * @param newValue the new value
125 */
126 @Override
127 public final void setText(final String newValue) {
128 setTextInternal(newValue);
129
130 HtmlInput.executeOnChangeHandlerIfAppropriate(this);
131 }
132
133 private void setTextInternal(final String newValue) {
134 initDefaultValue();
135 DomNode child = getFirstChild();
136 if (child == null) {
137 final DomText newChild = new DomText(getPage(), newValue);
138 appendChild(newChild);
139 }
140 else {
141 DomNode next = child.getNextSibling();
142 while (next != null && !(next instanceof DomText)) {
143 child = next;
144 next = child.getNextSibling();
145 }
146
147 if (next == null) {
148 removeChild(child);
149 final DomText newChild = new DomText(getPage(), newValue);
150 appendChild(newChild);
151 }
152 else {
153 ((DomText) next).setData(newValue);
154 }
155 }
156
157 final int pos = newValue.length();
158 setSelectionStart(pos);
159 setSelectionEnd(pos);
160 }
161
162 /**
163 * {@inheritDoc}
164 */
165 @Override
166 public NameValuePair[] getSubmitNameValuePairs() {
167 String text = getText();
168 text = text.replace("\r\n", "\n").replace("\n", "\r\n");
169
170 return new NameValuePair[]{new NameValuePair(getNameAttribute(), text)};
171 }
172
173 /**
174 * {@inheritDoc}
175 * @see SubmittableElement#reset()
176 */
177 @Override
178 public void reset() {
179 initDefaultValue();
180 setText(defaultValue_);
181 }
182
183 /**
184 * {@inheritDoc}
185 * @see SubmittableElement#setDefaultValue(String)
186 */
187 @Override
188 public void setDefaultValue(String defaultValue) {
189 initDefaultValue();
190 if (defaultValue == null) {
191 defaultValue = "";
192 }
193
194 // for FF, if value is still default value, change value too
195 if (getText().equals(getDefaultValue())) {
196 setTextInternal(defaultValue);
197 }
198 defaultValue_ = defaultValue;
199 }
200
201 /**
202 * {@inheritDoc}
203 * @see SubmittableElement#getDefaultValue()
204 */
205 @Override
206 public String getDefaultValue() {
207 initDefaultValue();
208 return defaultValue_;
209 }
210
211 /**
212 * {@inheritDoc} This implementation is empty; only checkboxes and radio buttons
213 * really care what the default checked value is.
214 * @see SubmittableElement#setDefaultChecked(boolean)
215 * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
216 * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
217 */
218 @Override
219 public void setDefaultChecked(final boolean defaultChecked) {
220 // Empty.
221 }
222
223 /**
224 * {@inheritDoc} This implementation returns {@code false}; only checkboxes and
225 * radio buttons really care what the default checked value is.
226 * @see SubmittableElement#isDefaultChecked()
227 * @see HtmlRadioButtonInput#isDefaultChecked()
228 * @see HtmlCheckBoxInput#isDefaultChecked()
229 */
230 @Override
231 public boolean isDefaultChecked() {
232 return false;
233 }
234
235 /**
236 * Returns the value of the attribute {@code name}. 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 name} or an empty string if that attribute isn't defined
241 */
242 public final String getNameAttribute() {
243 return getAttributeDirect(DomElement.NAME_ATTRIBUTE);
244 }
245
246 /**
247 * Returns the value of the attribute {@code rows}. 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 rows} or an empty string if that attribute isn't defined
252 */
253 public final String getRowsAttribute() {
254 return getAttributeDirect("rows");
255 }
256
257 /**
258 * Returns the value of the attribute {@code cols}. Refer to the
259 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
260 * documentation for details on the use of this attribute.
261 *
262 * @return the value of the attribute {@code cols} or an empty string if that attribute isn't defined
263 */
264 public final String getColumnsAttribute() {
265 return getAttributeDirect("cols");
266 }
267
268 /**
269 * {@inheritDoc}
270 */
271 @Override
272 public final boolean isDisabled() {
273 if (hasAttribute(ATTRIBUTE_DISABLED)) {
274 return true;
275 }
276
277 Node node = getParentNode();
278 while (node != null) {
279 if (node instanceof DisabledElement element
280 && element.isDisabled()) {
281 return true;
282 }
283 node = node.getParentNode();
284 }
285
286 return false;
287 }
288
289 /**
290 * {@inheritDoc}
291 */
292 @Override
293 public final String getDisabledAttribute() {
294 return getAttributeDirect(ATTRIBUTE_DISABLED);
295 }
296
297 /**
298 * Returns the value of the attribute {@code readonly}. Refer to the
299 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
300 * documentation for details on the use of this attribute.
301 *
302 * @return the value of the attribute {@code readonly} or an empty string if that attribute isn't defined
303 */
304 public final String getReadOnlyAttribute() {
305 return getAttributeDirect("readonly");
306 }
307
308 /**
309 * Returns the value of the attribute {@code tabindex}. Refer to the
310 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
311 * documentation for details on the use of this attribute.
312 *
313 * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
314 */
315 public final String getTabIndexAttribute() {
316 return getAttributeDirect("tabindex");
317 }
318
319 /**
320 * Returns the value of the attribute {@code accesskey}. Refer to the
321 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
322 * documentation for details on the use of this attribute.
323 *
324 * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
325 */
326 public final String getAccessKeyAttribute() {
327 return getAttributeDirect("accesskey");
328 }
329
330 /**
331 * Returns the value of the attribute {@code onfocus}. Refer to the
332 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
333 * documentation for details on the use of this attribute.
334 *
335 * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
336 */
337 public final String getOnFocusAttribute() {
338 return getAttributeDirect("onfocus");
339 }
340
341 /**
342 * Returns the value of the attribute {@code onblur}. Refer to the
343 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
344 * documentation for details on the use of this attribute.
345 *
346 * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
347 */
348 public final String getOnBlurAttribute() {
349 return getAttributeDirect("onblur");
350 }
351
352 /**
353 * Returns the value of the attribute {@code onselect}. Refer to the
354 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
355 * documentation for details on the use of this attribute.
356 *
357 * @return the value of the attribute {@code onselect} or an empty string if that attribute isn't defined
358 */
359 public final String getOnSelectAttribute() {
360 return getAttributeDirect("onselect");
361 }
362
363 /**
364 * Returns the value of the attribute {@code onchange}. Refer to the
365 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
366 * documentation for details on the use of this attribute.
367 *
368 * @return the value of the attribute {@code onchange} or an empty string if that attribute isn't defined
369 */
370 public final String getOnChangeAttribute() {
371 return getAttributeDirect("onchange");
372 }
373
374 /**
375 * {@inheritDoc}
376 */
377 @Override
378 public void select() {
379 selectionDelegate_.select();
380 }
381
382 /**
383 * {@inheritDoc}
384 */
385 @Override
386 public String getSelectedText() {
387 return selectionDelegate_.getSelectedText();
388 }
389
390 /**
391 * {@inheritDoc}
392 */
393 @Override
394 public int getSelectionStart() {
395 return selectionDelegate_.getSelectionStart();
396 }
397
398 /**
399 * {@inheritDoc}
400 */
401 @Override
402 public void setSelectionStart(final int selectionStart) {
403 selectionDelegate_.setSelectionStart(selectionStart);
404 }
405
406 /**
407 * {@inheritDoc}
408 */
409 @Override
410 public int getSelectionEnd() {
411 return selectionDelegate_.getSelectionEnd();
412 }
413
414 /**
415 * {@inheritDoc}
416 */
417 @Override
418 public void setSelectionEnd(final int selectionEnd) {
419 selectionDelegate_.setSelectionEnd(selectionEnd);
420 }
421
422 /**
423 * {@inheritDoc}
424 */
425 @Override
426 protected boolean printXml(final String indent, final boolean indentBefore, final PrintWriter printWriter) {
427 printWriter.print(indent + "<");
428 printOpeningTagContentAsXml(printWriter);
429
430 printWriter.print(">");
431 printWriter.print(StringUtils.escapeXml(getText()));
432 printWriter.print("</textarea>");
433 return true;
434 }
435
436 /**
437 * {@inheritDoc}
438 */
439 @Override
440 protected void doType(final char c, final boolean lastType) {
441 doTypeProcessor_.doType(getText(), selectionDelegate_, c, this, lastType);
442 }
443
444 /**
445 * {@inheritDoc}
446 */
447 @Override
448 protected void doType(final int keyCode, final boolean lastType) {
449 doTypeProcessor_.doType(getText(), selectionDelegate_, keyCode, this, lastType);
450 }
451
452 /**
453 * {@inheritDoc}
454 */
455 @Override
456 protected void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
457 setTextInternal(newValue);
458 }
459
460 /**
461 * {@inheritDoc}
462 */
463 @Override
464 protected boolean acceptChar(final char c) {
465 return super.acceptChar(c) || c == '\n' || c == '\r';
466 }
467
468 /**
469 * {@inheritDoc}
470 */
471 @Override
472 public void focus() {
473 super.focus();
474 valueAtFocus_ = getText();
475 }
476
477 /**
478 * {@inheritDoc}
479 */
480 @Override
481 public void removeFocus() {
482 super.removeFocus();
483 if (valueAtFocus_ != null && !valueAtFocus_.equals(getText())) {
484 HtmlInput.executeOnChangeHandlerIfAppropriate(this);
485 }
486 valueAtFocus_ = null;
487 }
488
489 /**
490 * Sets the {@code readOnly} attribute.
491 *
492 * @param isReadOnly {@code true} if this element is read only
493 */
494 public void setReadOnly(final boolean isReadOnly) {
495 if (isReadOnly) {
496 setAttribute("readonly", "");
497 }
498 else {
499 removeAttribute("readonly");
500 }
501 }
502
503 /**
504 * Returns {@code true} if this element is read only.
505 * @return {@code true} if this element is read only
506 */
507 public boolean isReadOnly() {
508 return hasAttribute("readonly");
509 }
510
511 /**
512 * {@inheritDoc}
513 * @return {@code true} to make generated XML readable as HTML
514 */
515 @Override
516 protected boolean isEmptyXmlTagExpanded() {
517 return true;
518 }
519
520 /**
521 * {@inheritDoc}
522 */
523 @Override
524 public DisplayStyle getDefaultStyleDisplay() {
525 return DisplayStyle.INLINE_BLOCK;
526 }
527
528 /**
529 * Returns the value of the {@code placeholder} attribute.
530 *
531 * @return the value of the {@code placeholder} attribute
532 */
533 public String getPlaceholder() {
534 return getAttributeDirect("placeholder");
535 }
536
537 /**
538 * Sets the {@code placeholder} attribute.
539 *
540 * @param placeholder the {@code placeholder} attribute
541 */
542 public void setPlaceholder(final String placeholder) {
543 setAttribute("placeholder", placeholder);
544 }
545
546 /**
547 * {@inheritDoc}
548 */
549 @Override
550 protected boolean isRequiredSupported() {
551 return true;
552 }
553
554 /**
555 * {@inheritDoc}
556 */
557 @Override
558 public DomNode cloneNode(final boolean deep) {
559 final HtmlTextArea newnode = (HtmlTextArea) super.cloneNode(deep);
560 newnode.selectionDelegate_ = new SelectableTextSelectionDelegate(newnode);
561 newnode.doTypeProcessor_ = new DoTypeProcessor(newnode);
562
563 return newnode;
564 }
565
566 /**
567 * {@inheritDoc}
568 */
569 @Override
570 public boolean willValidate() {
571 return !isDisabled() && !isReadOnly();
572 }
573
574 /**
575 * {@inheritDoc}
576 */
577 @Override
578 public void setCustomValidity(final String message) {
579 customValidity_ = message;
580 }
581
582 /**
583 * {@inheritDoc}
584 */
585 @Override
586 public boolean isValid() {
587 return isValidValidityState();
588 }
589
590 /**
591 * {@inheritDoc}
592 */
593 @Override
594 public boolean isCustomErrorValidityState() {
595 return !StringUtils.isEmptyOrNull(customValidity_);
596 }
597
598 @Override
599 public boolean isValidValidityState() {
600 return !isCustomErrorValidityState()
601 && !isValueMissingValidityState();
602 }
603
604 /**
605 * {@inheritDoc}
606 */
607 @Override
608 public boolean isValueMissingValidityState() {
609 return ATTRIBUTE_NOT_DEFINED != getAttributeDirect(ATTRIBUTE_REQUIRED)
610 && getText().isEmpty();
611 }
612 }