Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclassing from (extending) an HTML element?

I'd love it if I could make my own class that extended an HTML element class, e.g. HTMLDivElement or HTMLImageElement.

Assuming the standard inheritance pattern:

// class A:

function ClassA(foo) {
    this.foo = foo;
}

ClassA.prototype.toString = function() {
    return "My foo is " + this.foo;
};

// class B:

function ClassB(foo, bar) {
    ClassA.call(this, foo);
    this.bar = bar;
}

ClassB.prototype = new ClassA();

ClassB.prototype.toString = function() {
    return "My foo/bar is " + this.foo + "/" + this.bar;
};

I'm not able to figure out what to call in the constructor:

function Image2() {
    // this doesn't work:
    Image2.prototype.constructor.call(this);
    // neither does this (only applies to <img>, no equivalent for <span> etc.):
    Image.call(this);
}

Image2.prototype = new Image(); // or document.createElement("img");

Image2.prototype.toString = function() {
    return "My src is " + this.src;
};

Is this just not possible? Or is there some way? Thanks. =)

like image 676
Aseem Kishore Avatar asked Sep 06 '09 00:09

Aseem Kishore


2 Answers

Alright, this question needs an update from the current technology.

HTML5 offers ways for you to create custom elements. You need to register your element via document.registerElement and after that you can use it just like any other tags. The feature is already available in the latest Chrome. I'm not sure about other browsers.

More details here:

http://www.html5rocks.com/en/tutorials/webcomponents/customelements/

like image 140
ming_codes Avatar answered Sep 21 '22 09:09

ming_codes


I'm not able to figure out what to call in the constructor

Image2.prototype.constructor.call(this);

Yeah, JavaScript's objects don't have classes and chainable constructors like that. It has its own model of prototypes, which can be quite confusing, but doesn't actually deliver the proper potential of prototype inheritance because it's still tied to constructor-functions, which look like class constructors but aren't really.

unfortunately this isn't truly creating a subclass; it's just extending instances in disguise

Yes - that's all JavaScript gives you. If you want to, you can certainly create your own object system that abstracts this away so it seems like you're creating classes and instances. I've attached a flavour I often use at the bottom as an example. But there is no standard for creating a subclass of anything in JavaScript.

For example, in your "standard" code you say:

ClassA.call(this, foo);

but this only works because you have written ClassA in such a way that you can get away with calling it twice, once for the prototype ClassA, with 'foo' set to 'undefined', and then again on the object that inherits from that prototype, to mask that 'foo' behind another 'foo' over the top.

This is not anything actually standard that classes might be expected to do for each other, and it's certainly not specified that this will work for non-JS-native browser objects:

function MyImage() {
    Image.call(this); // might do anything. will almost certainly fail
}
MyImage.prototype= new Image();

In any case, setting a prototype to something like an HTMLElement that isn't a native-JavaScript object is not specified by the ECMAScript standard and has varying levels of success; in IE it doesn't work at all, the prototype's properties don't take.

Also,

Image2.prototype.constructor

from your example may actually not be the same as 'Image' as you would expect. 'constructor', apart from being a non-standard property that's not available in all browsers, does something that in a class system would be Not What You Think at all; it is best avoided.

Image2 should have all properties/methods of Image plus more

Currently the only way to do this is with a wrapper of some sort. You can't do this with prototypes for the reasons above; you'd have to add the properties to every instance separately:

function makeImage2() {
    var image= Image();
    image.isBig= function() {
        return this.width>200 || this.height>200;
    };
}

this is just a factory function of course, and not anything like subclassing. You would have to provide your own replacement for instanceof.

...

// An example class system
//
Function.prototype.subclass= function() {
    var c= new Function(
        'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
        'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
    );
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass._FLAG);
    return c;
};
Function.prototype.subclass._FLAG= {};

// Usage
//
ClassA= Object.subclass();
ClassA.prototype._init= function(foo) {
    this.foo= foo;
};
ClassA.prototype.toString= function() {
    return 'My foo is '+this.foo;
};

ClassB= ClassA.subclass();
ClassB.prototype._init= function(foo, bar) {
    ClassA.prototype._init.call(this, foo);
    this.bar= bar;
};
ClassB.prototype.toString= function() {
    return ClassA.prototype.toString.call(this)+' and my bar is '+this.bar;
};
like image 42
bobince Avatar answered Sep 21 '22 09:09

bobince