create a HTMLCollection
Asked Answered
S

4

22

I'm trying to shim Element.prototype.children which should return a HTMLCollection

There is a window.HTMLCollection

However

var h = new HTMLCollection();
//TypeErrror: HTMLCollection is not a constructor

and

var h = Object.create(HTMLCollection.prototype);
h[0] = div;
h.item(0); 
// Could not convert JavaScript argument

Test Firefox 7 and Chrome

Apart from shimming HTMLCollection is there any way to interact with it?

Also provide feedback on this github issue if you can suggest a solution

Septarium answered 13/10, 2011 at 12:40 Comment(3)
I believe the correct way to do this is to define your own custom MyHTMLCollection constructor and then use it instead of the host constructor HTMLCollection (which is not reliable)Bul
While I can't answer your specific question about HTMLCollection, it is generally considered bad practice to extend native DOM (hosted) objects. See this article for a detailed explanation why: perfectionkills.com/whats-wrong-with-extending-the-domYardmaster
@Yardmaster I'm perfectly aware of the consequences. We need to do this to generate a DOM-shim. We're not extending the DOM with custom methods (evil) but with methods that should exist as per the DOM4 specificationSeptarium
N
7

Here's how I would do it:

function MyHTMLCollection( arr ) {
    for ( var i = 0; i < arr.length; i += 1 ) {
        this[i] = arr[i];
    }

    // length is readonly
    Object.defineProperty( this, 'length', {
        get: function () {
            return arr.length;
        }
    });

    // a HTMLCollection is immutable
    Object.freeze( this );
}

MyHTMLCollection.prototype = {
    item: function ( i ) {
        return this[i] != null ? this[i] : null;
    },
    namedItem: function ( name ) {
        for ( var i = 0; i < this.length; i += 1 ) {
            if ( this[i].id === name || this[i].name === name ) {
                return this[i];
            }
        }
        return null;
    }
};

where arr is a regular array that contains all the DOM elements which should be inside the HTMLCollection.

To do list:

  • the argument arr should be checked beforehand: Is it an array? Are all elements of that array DOM elements?
Ninth answered 13/10, 2011 at 13:18 Comment(7)
What about instance[4] = newEl. length would fail. I think you need to calculate length at every get call for it to work.Septarium
@Septarium I just updated my answer. Object.freeze( this ) inside the constructor, but I'm not sure what the spec says about immutability of HTML collections. I'll check ...Bul
also Object.defineProperty only works on the DOM in IE8 so my dom-shim just broke in IE8. I guess I can make the length property a static value in IE8 and just forget about it.Septarium
@ŠimeVidas - check the spec, collections have a read only length.Pachyderm
@Pachyderm Yes, I defined it as an accessor property with no setter - this makes it read-only. { value: arr.length, writable: false } would be more proper but this works too.Bul
For the item function, it's probably better to do return this[i] || null;. That way you aren't accessing the internal array twice.Komsomolsk
@AlbertoAcuña Which browser?Bul
B
20

I think this is the proper way of creating HTMLCollection, which is handled by the browser.

var docFragment = document.createDocumentFragment();
docFragment.appendChild(node1);
docFragment.appendChild(node2);
var myHTMLCollection = docFragment.children;

Refs.:

https://mcmap.net/q/276840/-how-to-create-nodelist-object-from-two-or-more-domnodes

https://developer.mozilla.org/en-US/docs/Web/API/NodeList

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection

https://www.w3schools.com/js/js_htmldom_nodelist.asp

Boxberry answered 28/11, 2019 at 16:1 Comment(3)
nice! this is newer and more appropriate answer.Wangle
that is a very practical solutionLucifer
DocumentFragment must ensure its children detached from previous parent, hence not HTMLCollection counterpart.Skivvy
N
7

Here's how I would do it:

function MyHTMLCollection( arr ) {
    for ( var i = 0; i < arr.length; i += 1 ) {
        this[i] = arr[i];
    }

    // length is readonly
    Object.defineProperty( this, 'length', {
        get: function () {
            return arr.length;
        }
    });

    // a HTMLCollection is immutable
    Object.freeze( this );
}

MyHTMLCollection.prototype = {
    item: function ( i ) {
        return this[i] != null ? this[i] : null;
    },
    namedItem: function ( name ) {
        for ( var i = 0; i < this.length; i += 1 ) {
            if ( this[i].id === name || this[i].name === name ) {
                return this[i];
            }
        }
        return null;
    }
};

where arr is a regular array that contains all the DOM elements which should be inside the HTMLCollection.

To do list:

  • the argument arr should be checked beforehand: Is it an array? Are all elements of that array DOM elements?
Ninth answered 13/10, 2011 at 13:18 Comment(7)
What about instance[4] = newEl. length would fail. I think you need to calculate length at every get call for it to work.Septarium
@Septarium I just updated my answer. Object.freeze( this ) inside the constructor, but I'm not sure what the spec says about immutability of HTML collections. I'll check ...Bul
also Object.defineProperty only works on the DOM in IE8 so my dom-shim just broke in IE8. I guess I can make the length property a static value in IE8 and just forget about it.Septarium
@ŠimeVidas - check the spec, collections have a read only length.Pachyderm
@Pachyderm Yes, I defined it as an accessor property with no setter - this makes it read-only. { value: arr.length, writable: false } would be more proper but this works too.Bul
For the item function, it's probably better to do return this[i] || null;. That way you aren't accessing the internal array twice.Komsomolsk
@AlbertoAcuña Which browser?Bul
P
6

Don't expect host objects to behave like (ECMAScript) native objects, they are completely different things. Some browsers do implement their DOM objects like ECMAScript objects, but it is not required and should not be relied upon. Note that most HTML collections are live, it is very difficult to emulate that in a native object.

Pachyderm answered 13/10, 2011 at 12:53 Comment(1)
They are indeed incredibly difficult to emulate without a Proxy.Septarium
S
2

I know this is an older question but I came across a similar need to create an empty HTMLCollection and I did it by simply creating an element and then running getElementsByClassName() against it using a class that doesn't exist in the element.

document.createElement("div").getElementsByClassName('noClassHere');

This returns an empty HTMLCollection object.

Sorbose answered 14/11, 2017 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.