Consider I want to extend the native button
element, and create my own super-button
element. As I know, it must follow the following pattern:
var SuperButton = document.registerElement('super-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
It looks strange to me - doesn't the prototype
and extends
parameters say the same thing? If I explicitly say that my super-button
use the HTMLButtonElement
prototype, why do I also need to specify that it extends the button element? isn't it redundant? For me it looks like exactly the same information.
From the Custom Elements specification:
In general, the name of the element being extended cannot be determined simply by looking at what element interface it extends, as many elements share the same interface (such as q and blockquote both sharing HTMLQuoteElement).
In other words, while it may be redundant for <button>
elements, it isn't redundant in general and the spec needs to support the general case.
I would argue that it isn't even redundant for <button>
though, as there is nothing preventing you from doing:
var SuperButton = document.registerElement('super-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'a'
});
To clarify Paulpro's answer: the extends
creates the is=
syntax. If q
and blockquote
both share HTMLQuoteElement
, and you extend q
, then you can write <q is="super-button">
but cannot write <blockquote is="super-button">
.
Regarding what means to extend an a
link with another element's prototype
, this would create a link that does not have the appropriate functions and properties, which is bad:
See: https://jsfiddle.net/3g0pus8r/
<a is="anchor-one" href="google.com">1st link</a>
<a is="anchor-two" href="google.com">2nd link</a>
var proto1 = Object.create(HTMLAnchorElement.prototype, {
createdCallback: {
value: function() {
console.log("1st:");
console.log(HTMLAnchorElement.prototype.isPrototypeOf(this));
console.log(this.hostname);
console.log(this.toString());
}
}
});
var proto2 = Object.create(HTMLButtonElement.prototype, {
createdCallback: {
value: function() {
console.log("\n2nd:");
console.log(HTMLAnchorElement.prototype.isPrototypeOf(this));
console.log(this.hostname);
console.log(this.toString());
}
}
});
document.registerElement("anchor-one",{prototype:proto1, extends: "a"});
document.registerElement("anchor-two",{prototype:proto2, extends: "a"});
The result is:
1st:
true
fiddle.jshell.net
https://fiddle.jshell.net/_display/google.com
2st:
false
undefined
[object HTMLButtonElement]
However, you may still want to:
- Extend
MyOtherAnchor
which in turn extends HTMLAnchorElement
;
- Extend
HTMLElement
and implement all the anchor interface yourself.
- Create an anchor with a different, non-standard, interface.
Actually, its allows you to distinguish between Custom Tags declaration versus Type Extensions declaration (as suggested by Bergi's second comment), as both share the same method document.registerElement()
.
Type Extensions: use extends
document.registerElement( "button-v2", {
prototype: Object.create( HTMLButtonElement.prototype ),
extends: 'button'
} )
<button-v2>
will continue to act as a <button>
(ie keeps its semantics).
Custom Tags: don't use extends
document.registerElement( "not-button", {
prototype: Object.create( HTMLButtonElement.prototype )
} )
<not-button>
inherits from HTMLButtonElement
interface but lost its semantics (ie won't act as a <button>
)
NB: If the only reason was really because you cannot always infer an <element>
from its prototype chain, an optional parameter would have been proposed to disambiguate such rare cases. Here they have choosen a common, synthetic method, which is confusing at first sight you're right!