[w3c/webcomponents] Should I write new components backwards compatible with v0 API? (#549)

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:

```js
// 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
 * const WebComponent = WebComponentMixin(HTMLButtonElement)
 * class AwesomeButton extends WebComponent { ... }
 *
 * @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() { this.connectedCallback() } // back-compat

        disconnectedCallback() {
            // code removed for brevity...
        }
        detachedCallback() { this.disconnectedCallback() } // back-compat
    }

    classCache.set(elementClass, WebComponent)
    return WebComponent
}
```

Any thoughts?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/549

Received on Saturday, 20 August 2016 03:30:30 UTC