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 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 }