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.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) {
94                  final DomElement elem = (DomElement) next;
95                  final String nodeName = elem.getAttributeDirect(DomElement.NAME_ATTRIBUTE);
96                  if (name.equals(nodeName)) {
97                      elements.add(elem);
98                      continue;
99                  }
100 
101                 final String id = elem.getId();
102                 if (name.equals(id)) {
103                     elements.add(elem);
104                 }
105             }
106         }
107 
108         if (elements.isEmpty()) {
109             return null;
110         }
111         if (elements.size() == 1) {
112             return getScriptableForElement(elements.get(0));
113         }
114 
115         final RadioNodeList nodeList = new RadioNodeList(getDomNodeOrDie(), elements);
116         nodeList.setElementsSupplier(getElementSupplier());
117         return nodeList;
118     }
119 
120     /**
121      * Overridden to allow the retrieval of certain form elements by ID or name.
122      *
123      * @param cx {@inheritDoc}
124      * @param id {@inheritDoc}
125      * @return {@inheritDoc}
126      */
127     @Override
128     protected DescriptorInfo getOwnPropertyDescriptor(final Context cx, final Object id) {
129         final DescriptorInfo descInfo = super.getOwnPropertyDescriptor(cx, id);
130         if (descInfo != null) {
131             return descInfo;
132         }
133 
134         if (id instanceof CharSequence) {
135             final Scriptable element = namedItem(id.toString());
136             if (element != null) {
137                 return ScriptableObject.buildDataDescriptor(element, ScriptableObject.READONLY);
138             }
139         }
140 
141         return null;
142     }
143 
144     @JsxSymbol
145     @Override
146     public Scriptable iterator() {
147         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
148     }
149 }