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 *
123 * @param newValue the new value
124 */
125 @Override
126 public final void setText(final String newValue) {
127 setTextInternal(newValue);
128
129 HtmlInput.executeOnChangeHandlerIfAppropriate(this);
130 }
131
132 private void setTextInternal(final String newValue) {
133 initDefaultValue();
134 DomNode child = getFirstChild();
135 if (child == null) {
136 final DomText newChild = new DomText(getPage(), newValue);
137 appendChild(newChild);
138 }
139 else {
140 DomNode next = child.getNextSibling();
141 while (next != null && !(next instanceof DomText)) {
142 child = next;
143 next = child.getNextSibling();
144 }
145
146 if (next == null) {
147 removeChild(child);
148 final DomText newChild = new DomText(getPage(), newValue);
149 appendChild(newChild);
150 }
151 else {
152 ((DomText) next).setData(newValue);
153 }
154 }
155
156 final int pos = newValue.length();
157 setSelectionStart(pos);
158 setSelectionEnd(pos);
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 @Override
165 public NameValuePair[] getSubmitNameValuePairs() {
166 String text = getText();
167 text = text.replace("\r\n", "\n").replace("\n", "\r\n");
168
169 return new NameValuePair[]{new NameValuePair(getNameAttribute(), text)};
170 }
171
172 /**
173 * {@inheritDoc}
174 * @see SubmittableElement#reset()
175 */
176 @Override
177 public void reset() {
178 initDefaultValue();
179 setText(defaultValue_);
180 }
181
182 /**
183 * {@inheritDoc}
184 * @see SubmittableElement#setDefaultValue(String)
185 */
186 @Override
187 public void setDefaultValue(String defaultValue) {
188 initDefaultValue();
189 if (defaultValue == null) {
190 defaultValue = "";
191 }
192
193 // for FF, if value is still default value, change value too
194 if (getText().equals(getDefaultValue())) {
195 setTextInternal(defaultValue);
196 }
197 defaultValue_ = defaultValue;
198 }
199
200 /**
201 * {@inheritDoc}
202 * @see SubmittableElement#getDefaultValue()
203 */
204 @Override
205 public String getDefaultValue() {
206 initDefaultValue();
207 return defaultValue_;
208 }
209
210 /**
211 * {@inheritDoc} This implementation is empty; only checkboxes and radio buttons
212 * really care what the default checked value is.
213 * @see SubmittableElement#setDefaultChecked(boolean)
214 * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
215 * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
216 */
217 @Override
218 public void setDefaultChecked(final boolean defaultChecked) {
219 // Empty.
220 }
221
222 /**
223 * {@inheritDoc} This implementation returns {@code false}; only checkboxes and
224 * radio buttons really care what the default checked value is.
225 * @see SubmittableElement#isDefaultChecked()
226 * @see HtmlRadioButtonInput#isDefaultChecked()
227 * @see HtmlCheckBoxInput#isDefaultChecked()
228 */
229 @Override
230 public boolean isDefaultChecked() {
231 return false;
232 }
233
234 /**
235 * Returns the value of the attribute {@code name}. Refer to the
236 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
237 * documentation for details on the use of this attribute.
238 *
239 * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
240 */
241 public final String getNameAttribute() {
242 return getAttributeDirect(DomElement.NAME_ATTRIBUTE);
243 }
244
245 /**
246 * Returns the value of the attribute {@code rows}. Refer to the
247 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
248 * documentation for details on the use of this attribute.
249 *
250 * @return the value of the attribute {@code rows} or an empty string if that attribute isn't defined
251 */
252 public final String getRowsAttribute() {
253 return getAttributeDirect("rows");
254 }
255
256 /**
257 * Returns the value of the attribute {@code cols}. Refer to the
258 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
259 * documentation for details on the use of this attribute.
260 *
261 * @return the value of the attribute {@code cols} or an empty string if that attribute isn't defined
262 */
263 public final String getColumnsAttribute() {
264 return getAttributeDirect("cols");
265 }
266
267 /**
268 * {@inheritDoc}
269 */
270 @Override
271 public final boolean isDisabled() {
272 if (hasAttribute(ATTRIBUTE_DISABLED)) {
273 return true;
274 }
275
276 Node node = getParentNode();
277 while (node != null) {
278 if (node instanceof DisabledElement element
279 && element.isDisabled()) {
280 return true;
281 }
282 node = node.getParentNode();
283 }
284
285 return false;
286 }
287
288 /**
289 * {@inheritDoc}
290 */
291 @Override
292 public final String getDisabledAttribute() {
293 return getAttributeDirect(ATTRIBUTE_DISABLED);
294 }
295
296 /**
297 * Returns the value of the attribute {@code readonly}. Refer to the
298 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
299 * documentation for details on the use of this attribute.
300 *
301 * @return the value of the attribute {@code readonly} or an empty string if that attribute isn't defined
302 */
303 public final String getReadOnlyAttribute() {
304 return getAttributeDirect("readonly");
305 }
306
307 /**
308 * Returns the value of the attribute {@code tabindex}. Refer to the
309 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
310 * documentation for details on the use of this attribute.
311 *
312 * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
313 */
314 public final String getTabIndexAttribute() {
315 return getAttributeDirect("tabindex");
316 }
317
318 /**
319 * Returns the value of the attribute {@code accesskey}. Refer to the
320 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
321 * documentation for details on the use of this attribute.
322 *
323 * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
324 */
325 public final String getAccessKeyAttribute() {
326 return getAttributeDirect("accesskey");
327 }
328
329 /**
330 * Returns the value of the attribute {@code onfocus}. Refer to the
331 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
332 * documentation for details on the use of this attribute.
333 *
334 * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
335 */
336 public final String getOnFocusAttribute() {
337 return getAttributeDirect("onfocus");
338 }
339
340 /**
341 * Returns the value of the attribute {@code onblur}. Refer to the
342 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
343 * documentation for details on the use of this attribute.
344 *
345 * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
346 */
347 public final String getOnBlurAttribute() {
348 return getAttributeDirect("onblur");
349 }
350
351 /**
352 * Returns the value of the attribute {@code onselect}. Refer to the
353 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
354 * documentation for details on the use of this attribute.
355 *
356 * @return the value of the attribute {@code onselect} or an empty string if that attribute isn't defined
357 */
358 public final String getOnSelectAttribute() {
359 return getAttributeDirect("onselect");
360 }
361
362 /**
363 * Returns the value of the attribute {@code onchange}. Refer to the
364 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
365 * documentation for details on the use of this attribute.
366 *
367 * @return the value of the attribute {@code onchange} or an empty string if that attribute isn't defined
368 */
369 public final String getOnChangeAttribute() {
370 return getAttributeDirect("onchange");
371 }
372
373 /**
374 * {@inheritDoc}
375 */
376 @Override
377 public void select() {
378 selectionDelegate_.select();
379 }
380
381 /**
382 * {@inheritDoc}
383 */
384 @Override
385 public String getSelectedText() {
386 return selectionDelegate_.getSelectedText();
387 }
388
389 /**
390 * {@inheritDoc}
391 */
392 @Override
393 public int getSelectionStart() {
394 return selectionDelegate_.getSelectionStart();
395 }
396
397 /**
398 * {@inheritDoc}
399 */
400 @Override
401 public void setSelectionStart(final int selectionStart) {
402 selectionDelegate_.setSelectionStart(selectionStart);
403 }
404
405 /**
406 * {@inheritDoc}
407 */
408 @Override
409 public int getSelectionEnd() {
410 return selectionDelegate_.getSelectionEnd();
411 }
412
413 /**
414 * {@inheritDoc}
415 */
416 @Override
417 public void setSelectionEnd(final int selectionEnd) {
418 selectionDelegate_.setSelectionEnd(selectionEnd);
419 }
420
421 /**
422 * {@inheritDoc}
423 */
424 @Override
425 protected boolean printXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
426 printWriter.print(indent + "<");
427 printOpeningTagContentAsXml(printWriter);
428
429 printWriter.print(">");
430 printWriter.print(StringUtils.escapeXml(getText()));
431 printWriter.print("</textarea>");
432 return true;
433 }
434
435 /**
436 * {@inheritDoc}
437 */
438 @Override
439 protected void doType(final char c, final boolean lastType) {
440 doTypeProcessor_.doType(getText(), selectionDelegate_, c, this, lastType);
441 }
442
443 /**
444 * {@inheritDoc}
445 */
446 @Override
447 protected void doType(final int keyCode, final boolean lastType) {
448 doTypeProcessor_.doType(getText(), selectionDelegate_, keyCode, this, lastType);
449 }
450
451 /**
452 * {@inheritDoc}
453 */
454 @Override
455 protected void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
456 setTextInternal(newValue);
457 }
458
459 /**
460 * {@inheritDoc}
461 */
462 @Override
463 protected boolean acceptChar(final char c) {
464 return super.acceptChar(c) || c == '\n' || c == '\r';
465 }
466
467 /**
468 * {@inheritDoc}
469 */
470 @Override
471 public void focus() {
472 super.focus();
473 valueAtFocus_ = getText();
474 }
475
476 /**
477 * {@inheritDoc}
478 */
479 @Override
480 public void removeFocus() {
481 super.removeFocus();
482 if (valueAtFocus_ != null && !valueAtFocus_.equals(getText())) {
483 HtmlInput.executeOnChangeHandlerIfAppropriate(this);
484 }
485 valueAtFocus_ = null;
486 }
487
488 /**
489 * Sets the {@code readOnly} attribute.
490 *
491 * @param isReadOnly {@code true} if this element is read only
492 */
493 public void setReadOnly(final boolean isReadOnly) {
494 if (isReadOnly) {
495 setAttribute("readOnly", "readOnly");
496 }
497 else {
498 removeAttribute("readOnly");
499 }
500 }
501
502 /**
503 * Returns {@code true} if this element is read only.
504 * @return {@code true} if this element is read only
505 */
506 public boolean isReadOnly() {
507 return hasAttribute("readOnly");
508 }
509
510 /**
511 * {@inheritDoc}
512 * @return {@code true} to make generated XML readable as HTML
513 */
514 @Override
515 protected boolean isEmptyXmlTagExpanded() {
516 return true;
517 }
518
519 /**
520 * {@inheritDoc}
521 */
522 @Override
523 public DisplayStyle getDefaultStyleDisplay() {
524 return DisplayStyle.INLINE_BLOCK;
525 }
526
527 /**
528 * Returns the value of the {@code placeholder} attribute.
529 *
530 * @return the value of the {@code placeholder} attribute
531 */
532 public String getPlaceholder() {
533 return getAttributeDirect("placeholder");
534 }
535
536 /**
537 * Sets the {@code placeholder} attribute.
538 *
539 * @param placeholder the {@code placeholder} attribute
540 */
541 public void setPlaceholder(final String placeholder) {
542 setAttribute("placeholder", placeholder);
543 }
544
545 /**
546 * {@inheritDoc}
547 */
548 @Override
549 protected boolean isRequiredSupported() {
550 return true;
551 }
552
553 /**
554 * {@inheritDoc}
555 */
556 @Override
557 public DomNode cloneNode(final boolean deep) {
558 final HtmlTextArea newnode = (HtmlTextArea) super.cloneNode(deep);
559 newnode.selectionDelegate_ = new SelectableTextSelectionDelegate(newnode);
560 newnode.doTypeProcessor_ = new DoTypeProcessor(newnode);
561
562 return newnode;
563 }
564
565 /**
566 * {@inheritDoc}
567 */
568 @Override
569 public boolean willValidate() {
570 return !isDisabled() && !isReadOnly();
571 }
572
573 /**
574 * {@inheritDoc}
575 */
576 @Override
577 public void setCustomValidity(final String message) {
578 customValidity_ = message;
579 }
580
581 /**
582 * {@inheritDoc}
583 */
584 @Override
585 public boolean isValid() {
586 return isValidValidityState();
587 }
588
589 /**
590 * {@inheritDoc}
591 */
592 @Override
593 public boolean isCustomErrorValidityState() {
594 return !StringUtils.isEmptyOrNull(customValidity_);
595 }
596
597 @Override
598 public boolean isValidValidityState() {
599 return !isCustomErrorValidityState()
600 && !isValueMissingValidityState();
601 }
602
603 /**
604 * {@inheritDoc}
605 */
606 @Override
607 public boolean isValueMissingValidityState() {
608 return ATTRIBUTE_NOT_DEFINED != getAttributeDirect(ATTRIBUTE_REQUIRED)
609 && getText().isEmpty();
610 }
611 }