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