Tim
Tim

Reputation: 83

Can I subclass a DOM-class?

I was wondering if I can create a subclass of HTMLDivElement. Like this.

MyDivElement.prototype.pickColor = function()
{
    return this.picked;
}
function MyDivElement()
{
    this = new HTMLDivElement();
    this.picked = 'unknowd';
}
alert(this.picked); // print: 'unkowd'

Is (something like) this possible? If not, what is the best way to achieve this?

Upvotes: 8

Views: 1597

Answers (4)

Fordi
Fordi

Reputation: 2876

I'm experimenting with this a little bit. A big difficulty is that you need the context of a document to create an element. You can go with window.document as a default, but that's boring.

Here's the POC I'm working on:

function CustomNode(type, parent) { 
    if (type instanceof Node) {
        // Decorate a preexisting node appropriately if called that way.
        if (arguments.length === 2 && type.ownerDocument !== parent) {
            // Import the node if it's not owned by the requested document.
            type = parent.importNode(type, true);
        }
        return Object.assign(type, this.__proto__);
    }
    //Normal flow, e.g., new CustomNode("div");
    var d = document;
    if (parent) {
        // Alt document flow, e.g., new CustomNode("div", xmlDoc);
        if (parent.nodeType === 9) {
            d = parent;
        } else {
            // Support for new CustomNode("div", parentElement);
            //  This doesn't append the element, just makes sure 
            //  the documents match
            d = parent.ownerDocument;
        }
    }
    var inst;
    // Creation flags
    if (type[0] === '#') { //text
        inst = d.createTextNode(type.substr(1));
    } else if (type[0] === '?') { //Processing instruction
        type = type.substr(1).split(' ');
        inst = d.createProcessingInstruction(type.shift(), type.join(' '));
    } else if (type[0] === '[') { // CDATA
        inst = d.createCDATASection(type.substr(1));
    } else if (type[0] === '/') { // Comment
        inst = d.createComment(type.substr(1));
    } else { //Everything else gets an element.
        inst = d.createElement(type);
    }
    // DE-COR-ATE
    Object.assign(inst, this.__proto__);
    return inst;
}
// Decorator for customized NodeLists; probably inefficient.  Decorates
//   contents with CustomNode
function CustomNodeList(list) {
    var Self = this.constructor,
        CNode = this.Node;
    return Object.assign([].map.call(list, function (node) {
        return new CNode(node);
    }), this.__proto__);
}
CustomNodeList.prototype = {
    // so we know how to wrap...
    Node: CustomNode,
    text: function () {
        return this[0].textContent;
    }
};


CustomNode.prototype = {
    // So we know how to decorate NodeLists
    NodeList: CustomNodeList,
    // So we know how to decorate Nodes
    Node: CustomNode,
    // Easy make-and-attach
    new: function (type, attach) {
        var CNode = this.Node;
        var ret = new CNode(type, this.ownerDocument);
        if (attach) {
            this.appendChild(ret);
        }
        return ret;
    },
    // NodeLists with ES5 iterators!
    find: function () {
        var CNodeList = this.NodeList;
        return new CNodeList(this.querySelectorAll.apply(this, arguments));
    },
    kids: function () {
        var CNodeList = this.NodeList;
        return new CNodeList(this.childNodes);
    }
};

Mind, this is likely a bad idea: everything in the same scope of these functions (including the elements themselves) will never get garbage collected, as the GC in most browsers is dead-stupid when it comes to elements referencing objects. I'll never use it for production for that reason alone: it's a straight-up memory leak.

Upvotes: 1

Jake
Jake

Reputation: 558

In browsers where __proto__ is exposed and mutable you can sub class DOM elements. It looks like this:

function CustomEl () {
    var el = document.createElement('div')
    el.__proto__ = CustomEl.prototype
    return el
}
CustomEl.prototype.__proto__ = HTMLDivElement.prototype

I also played with it in more detail on jsFiddle. Unfortunately though IE and Opera don't allow __proto__ access and have no plans to in the future as far as I know.

Upvotes: 6

Šime Vidas
Šime Vidas

Reputation: 185893

new HTMLDivElement(); throws a TypError "Illegal constructor" in Chrome - so it's not possible.

Update: I've tested in other current browsers, and they throw various types of errors - but they all throw.


Actually, this would work:

function MyDivElement() {
    this.picked = 'unknowd';
}

MyDivElement.prototype = document.createElement('div');

var mydiv = new MyDivElement();

But I'm not sure how you could use this pattern...

Upvotes: 2

Pointy
Pointy

Reputation: 413712

In some browsers, you can extend the prototype, in others, no. I'll let you guess the ones where you can't. :-) That's not really the same as extending a DOM element, but it does let you do a certain subset of the things for which you might want that facility. The thing is, DOM elements aren't really JavaScript entities; they're only simulacrums provided by the runtime system. (Maybe someday all the jsdom work will actually come to fruition.)

Well ok I'll tell you about the problematic browsers: IE doesn't like that at all. However others do. If you've ever looked at the Prototype library, you'll come across a manifestation of that fact all the time via nasty irritating IE-only bugs when you forget to Prototype-ize a DOM element reference.

(IE9 may be different, but I sort-of doubt it.)

This is the kind of thing that's dirt simple to test over at jsfiddle.

Upvotes: 1

Related Questions