diff --git a/src/dom.js b/src/dom.js index 2b81750148a3..7b3aae97b961 100644 --- a/src/dom.js +++ b/src/dom.js @@ -16,6 +16,7 @@ import {dashToCamelCase} from './string'; import {dev} from './log'; +import {toArray} from './types'; /** * Waits until the child element is constructed. Once the child is found, the @@ -328,13 +329,7 @@ export function childElementsByAttr(parent, attr) { scopeSelectorSupported = isScopeSelectorSupported(parent); } if (scopeSelectorSupported) { - const nodeList = parent.querySelectorAll(':scope > [' + attr + ']'); - // Convert NodeList into Array.. - const children = []; - for (let i = 0; i < nodeList.length; i++) { - children.push(nodeList[i]); - } - return children; + return toArray(parent.querySelectorAll(':scope > [' + attr + ']')); } return childElements(parent, el => { return el.hasAttribute(attr); diff --git a/src/types.js b/src/types.js index 45c835aa58ce..400bbb20f41d 100644 --- a/src/types.js +++ b/src/types.js @@ -35,6 +35,23 @@ export function isArray(value) { return Array.isArray(value); } +/** + * Converts an array-like object to an array. + * @param {?IArrayLike|string} arrayLike + * @return {!Array} + * @template T + */ +export function toArray(arrayLike) { + if (!arrayLike) { + return []; + } + const array = new Array(arrayLike.length); + for (let i = 0; i < arrayLike.length; i++) { + array[i] = arrayLike[i]; + } + return array; +} + /** * Determines if value is actually an Object. * @param {*} value diff --git a/test/functional/test-types.js b/test/functional/test-types.js new file mode 100644 index 000000000000..884d94ea5bea --- /dev/null +++ b/test/functional/test-types.js @@ -0,0 +1,62 @@ +/** + * Copyright 2016 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as types from '../../src/types'; + +describe('Types', () => { + describe('toArray', () => { + + it('should return empty array if null is passed', () => { + expect(types.toArray(null).length).to.equal(0); + expect(types.toArray(undefined).length).to.equal(0); + }); + + it('should convert NodeList to array', () => { + const parent = document.createElement('div'); + parent.appendChild(document.createElement('p')); + parent.appendChild(document.createElement('span')); + parent.appendChild(document.createElement('div')); + const arr = types.toArray(parent.childNodes); + expect(arr[0]).to.equal(parent.childNodes[0]); + expect(arr.length).to.equal(3); + expect(Array.isArray(arr)).to.be.true; + }); + + it('should convert HTMLCollection to array', () => { + const parent = document.createElement('div'); + parent.appendChild(document.createElement('form')); + parent.appendChild(document.createElement('form')); + document.body.appendChild(parent); + const arr = types.toArray(document.forms); + expect(arr[0]).to.equal(document.forms[0]); + expect(arr.length).to.equal(2); + expect(Array.isArray(arr)).to.be.true; + document.body.removeChild(parent); + }); + + it('should convert HTMLOptionsCollection to array', () => { + const parent = document.createElement('select'); + parent.appendChild(document.createElement('option')); + parent.appendChild(document.createElement('option')); + parent.appendChild(document.createElement('option')); + parent.appendChild(document.createElement('option')); + const arr = types.toArray(parent.options); + expect(arr[0]).to.equal(parent.options[0]); + expect(arr.length).to.equal(4); + expect(Array.isArray(arr)).to.be.true; + }); + }); +});