Re: [Custom Elements] Should I write v1 Custom Elements in a way backwards compatible with v0 API?

2016-08-22 Thread Dominic Cooney
I implemented custom elements in Chrome (twice.) This looks reasonable to
me.

The exact timing of createdCallback and constructor running, and errors
around element creation, are different. If authors stick to the
restrictions of custom elements "v1" they should be fine, because they're
more restrictive than v0.

On Sun, Aug 21, 2016 at 11:13 AM, /#!/JoePea  wrote:

> Due to the renaming of some class methods (attached/detached to
> connected/disconnected) and removal of createdCallback in favor of
> constructors (which is a good thing!), I find myself writing my
> WebComponent base class (class-factory mixin) as follows.
>
> My question is, should I be doing what I'm doing, or do you recommend
> something else? Note that the attached/detachedCallback methods simply call
> the connected/disconnectedCallback methods, and also note what I've done
> with `createdCallback` and the `constructor` due to the differences between
> v0 and v1. Subclasses of my WebComponent base class still use
> `createdCallback` rather than a constructor in order to be backwards
> compatible.
>
> The code is as follows, with parts removed for brevity in order to show
> just the parts dealing with the v0/v1 APIs directly and how backwards
> compatibility is maintained:
>
> // Very very stupid hack needed for Safari in order for us to be able
> to extend
> // the HTMLElement class. See:
> // https://github.com/google/traceur-compiler/issues/1709
> if (typeof window.HTMLElement != 'function') {
> const _HTMLElement = function HTMLElement(){}
> _HTMLElement.prototype = window.HTMLElement.prototype
> window.HTMLElement = _HTMLElement
> }
>
> // XXX: Maybe we can improve by clearing items after X amount of time?
> const classCache = new Map
>
> function hasHTMLElementPrototype(constructor) {
> if (!constructor) return false
> if (constructor === HTMLElement) return true
> else return hasHTMLElementPrototype(constructor.prototype)
> }
>

I'm not sure this is right. Would it be simpler anyway to just say
constructor && (constructor === HTMLElement || constructor.prototype
instanceof HTMLElement)?


> /**
>  * Creates a WebComponent base class dynamically, depending on which
>  * HTMLElement class you want it to extend from. Extend from
> WebComponent when
>  * making a new Custom Element class.
>  *
>  * @example
>  * import WebComponent from './WebComponent'
>  * class AwesomeButton extends WebComponent(HTMLButtonElement) { ... }
>  *
>  * @param {Function} elementClass The class that the generated
> WebComponent
>  * base class will extend from.
>  */
> export default
> function WebComponentMixin(elementClass) {
> if (!elementClass) elementClass = HTMLElement
>
> if (!hasHTMLElementPrototype(elementClass)) {
> throw new TypeError(
> 'The argument to WebComponentMixin must be a constructor
> that extends from or is HTMLElement.'
> )
> }
>
> // if a base class that extends the given `elementClass` has
> already been
> // created, return it.
> if (classCache.has(elementClass))
> return classCache.get(elementClass)
>
> // otherwise, create it.
> class WebComponent extends elementClass {
>
> // constructor() is used in v1 Custom Elements instead of
> // createdCallback() as in v0.
> constructor() {
> super()
>
> // If the following is true, then we know the user should
> be using
> // `document.registerElement()` to define an element from
> this class.
> // `document.registerElement()` creates a new constructor,
> so if the
> // constructor here is being called then that means the
> user is not
> // instantiating a DOM HTMLElement as expected because it
> is required
> // that the constructor returned from
> `document.registerElement` be used
> // instead (this is a flaw of Custom Elements v0 which is
> fixed in v1
> // where class constructors can be used directly).
> if (document.registerElement && !customElements.define) {
>
> // TODO: link to docs.
> throw new Error(`
> You cannot instantiate this class directly without
> first registering it
> with \`document.registerElement(...)\`. See an
> example at http://
> `)
>
> }
>
> // Throw an error if no Custom Elements API exists.
> if (!document.registerElement && !customElements.define) {
>
> // TODO: link to docs.
> throw new Error(`
> Your browser does not support the Custom Elements
> API. You'll
> n

[Custom Elements] Should I write v1 Custom Elements in a way backwards compatible with v0 API?

2016-08-20 Thread /#!/JoePea
Due to the renaming of some class methods (attached/detached to
connected/disconnected) and removal of createdCallback in favor of
constructors (which is a good thing!), I find myself writing my
WebComponent base class (class-factory mixin) as follows.

My question is, should I be doing what I'm doing, or do you recommend
something else? Note that the attached/detachedCallback methods simply call
the connected/disconnectedCallback methods, and also note what I've done
with `createdCallback` and the `constructor` due to the differences between
v0 and v1. Subclasses of my WebComponent base class still use
`createdCallback` rather than a constructor in order to be backwards
compatible.

The code is as follows, with parts removed for brevity in order to show
just the parts dealing with the v0/v1 APIs directly and how backwards
compatibility is maintained:

// Very very stupid hack needed for Safari in order for us to be able
to extend
// the HTMLElement class. See:
// https://github.com/google/traceur-compiler/issues/1709
if (typeof window.HTMLElement != 'function') {
const _HTMLElement = function HTMLElement(){}
_HTMLElement.prototype = window.HTMLElement.prototype
window.HTMLElement = _HTMLElement
}

// XXX: Maybe we can improve by clearing items after X amount of time?
const classCache = new Map

function hasHTMLElementPrototype(constructor) {
if (!constructor) return false
if (constructor === HTMLElement) return true
else return hasHTMLElementPrototype(constructor.prototype)
}

/**
 * Creates a WebComponent base class dynamically, depending on which
 * HTMLElement class you want it to extend from. Extend from
WebComponent when
 * making a new Custom Element class.
 *
 * @example
 * import WebComponent from './WebComponent'
 * class AwesomeButton extends WebComponent(HTMLButtonElement) { ... }
 *
 * @param {Function} elementClass The class that the generated
WebComponent
 * base class will extend from.
 */
export default
function WebComponentMixin(elementClass) {
if (!elementClass) elementClass = HTMLElement

if (!hasHTMLElementPrototype(elementClass)) {
throw new TypeError(
'The argument to WebComponentMixin must be a constructor
that extends from or is HTMLElement.'
)
}

// if a base class that extends the given `elementClass` has
already been
// created, return it.
if (classCache.has(elementClass))
return classCache.get(elementClass)

// otherwise, create it.
class WebComponent extends elementClass {

// constructor() is used in v1 Custom Elements instead of
// createdCallback() as in v0.
constructor() {
super()

// If the following is true, then we know the user should
be using
// `document.registerElement()` to define an element from
this class.
// `document.registerElement()` creates a new constructor,
so if the
// constructor here is being called then that means the
user is not
// instantiating a DOM HTMLElement as expected because it
is required
// that the constructor returned from
`document.registerElement` be used
// instead (this is a flaw of Custom Elements v0 which is
fixed in v1
// where class constructors can be used directly).
if (document.registerElement && !customElements.define) {

// TODO: link to docs.
throw new Error(`
You cannot instantiate this class directly without
first registering it
with \`document.registerElement(...)\`. See an
example at http://
`)

}

// Throw an error if no Custom Elements API exists.
if (!document.registerElement && !customElements.define) {

// TODO: link to docs.
throw new Error(`
Your browser does not support the Custom Elements
API. You'll
need to install a polyfill. See how at http://
`)

}

// otherwise the V1 API exists, so call the
createdCallback, which
// is what Custom Elements v0 would call by default.
Subclasses of
// WebComponent should put instantiation logic in
createdCallback
// instead of in a custom constructor if backwards
compatibility is
// to be maintained.
this.createdCallback()
}

createdCallback() {
// code removed for brevity...
}

connectedCallback() {
// code removed for brevity...
}
attachedCallback() { t