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