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.javascript.host.html;
16  
17  import java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.corejs.javascript.Context;
21  import org.htmlunit.corejs.javascript.Scriptable;
22  import org.htmlunit.corejs.javascript.ScriptableObject;
23  import org.htmlunit.html.DomElement;
24  import org.htmlunit.html.DomNode;
25  import org.htmlunit.javascript.JavaScriptEngine;
26  import org.htmlunit.javascript.configuration.JsxClass;
27  import org.htmlunit.javascript.configuration.JsxConstructor;
28  import org.htmlunit.javascript.configuration.JsxFunction;
29  import org.htmlunit.javascript.configuration.JsxSymbol;
30  import org.htmlunit.javascript.host.dom.RadioNodeList;
31  
32  /**
33   * A JavaScript object for {@code HTMLFormControlsCollection}.
34   *
35   * @author Ahmed Ashour
36   * @author Ronald Brill
37   * @author Lai Quang Duong
38   */
39  @JsxClass
40  public class HTMLFormControlsCollection extends HTMLCollection {
41  
42      /**
43       * Creates an instance.
44       */
45      public HTMLFormControlsCollection() {
46          super();
47      }
48  
49      /**
50       * Creates an instance.
51       * @param domNode parent scope
52       * @param attributeChangeSensitive indicates if the content of the collection may change when an attribute
53       *        of a descendant node of parentScope changes (attribute added, modified or removed)
54       */
55      public HTMLFormControlsCollection(final DomNode domNode, final boolean attributeChangeSensitive) {
56          super(domNode, attributeChangeSensitive);
57      }
58  
59      /**
60       * Constructs an instance with an initial cache value.
61       * @param domNode the parent scope, on which we listen for changes
62       * @param initialElements the initial content for the cache
63       */
64      HTMLFormControlsCollection(final DomNode domNode, final List<DomNode> initialElements) {
65          super(domNode, initialElements);
66      }
67  
68      /**
69       * JavaScript constructor.
70       */
71      @Override
72      @JsxConstructor
73      public void jsConstructor() {
74          super.jsConstructor();
75      }
76  
77      /**
78       * Returns the element with ID or name match the specified value from the collection.
79       * If there are multiple matching elements, then a RadioNodeList object containing all those elements is returned.
80       * @param name the name or id the element or elements to return
81       * @return the element or elements corresponding to the specified name or id
82       * @see <a href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#the-htmlformcontrolscollection-interface">HTML Standard</a>
83       */
84      @Override
85      @JsxFunction
86      public Scriptable namedItem(final String name) {
87          if (name.isEmpty()) {
88              return null;
89          }
90  
91          final List<DomNode> elements = new ArrayList<>();
92          for (final Object next : getElements()) {
93              if (next instanceof DomElement elem) {
94                  final String nodeName = elem.getAttributeDirect(DomElement.NAME_ATTRIBUTE);
95                  if (name.equals(nodeName)) {
96                      elements.add(elem);
97                      continue;
98                  }
99  
100                 final String id = elem.getId();
101                 if (name.equals(id)) {
102                     elements.add(elem);
103                 }
104             }
105         }
106 
107         if (elements.isEmpty()) {
108             return null;
109         }
110         if (elements.size() == 1) {
111             return getScriptableForElement(elements.get(0));
112         }
113 
114         final RadioNodeList nodeList = new RadioNodeList(getDomNodeOrDie(), elements);
115         nodeList.setElementsSupplier(getElementSupplier());
116         return nodeList;
117     }
118 
119     /**
120      * Overridden to allow the retrieval of certain form elements by ID or name.
121      *
122      * @param cx {@inheritDoc}
123      * @param id {@inheritDoc}
124      * @return {@inheritDoc}
125      */
126     @Override
127     protected DescriptorInfo getOwnPropertyDescriptor(final Context cx, final Object id) {
128         final DescriptorInfo descInfo = super.getOwnPropertyDescriptor(cx, id);
129         if (descInfo != null) {
130             return descInfo;
131         }
132 
133         if (id instanceof CharSequence) {
134             final Scriptable element = namedItem(id.toString());
135             if (element != null) {
136                 return ScriptableObject.buildDataDescriptor(element, ScriptableObject.READONLY);
137             }
138         }
139 
140         return null;
141     }
142 
143     @JsxSymbol
144     @Override
145     public Scriptable iterator() {
146         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
147     }
148 }