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