- From: Dominic Cooney <dominicc@chromium.org>
- Date: Tue, 30 Aug 2011 22:33:16 -0700
- To: WebApps WG <public-webapps@w3.org>
"Components" (see http://wiki.whatwg.org/wiki/Component_Model_Use_Cases for examples of what I mean) need to present an API to script. For example, a "contacts" component might want to expose a refresh() method that pulls new contacts from the server. Components also need to hook up internal behavior implemented in script; a split pane might need to hook up a mouse listener on its splitter before it can really behave like a split pane. In script widget libraries today (YUI, Closure, etc.) this is easy when the widget has been created from script: when the author calls the constructor/factory method/whatever the widget can create DOM, hook up event listeners, set up the prototype chain however it wants, etc. However things get really murky when authors start to use markup, because then the parser is creating elements that are at best only minimally functional until they're "enhanced." So today widget implementations need to handle enhancing an existing element; frameworks need to detect these unenhanced elements and invoke something to enhance them; and authors needs to debug this unholy mess when an unenhanced element escapes and their page breaks. Wouldn't it be nice if the browser made this problem go away? I think HTML itself shows us how life could be better: Consider the HTML video element. It looks like a "subtype" of HTML element, with all of the methods and attributes a HTML element has (via the prototype chain), and adds methods and attributes specific for doing video. When you do document.createElement('video') you get back an object that is ready to use. There's no step to "enhance" it. When you do p.innerHTML = "<video src=… controls=true>", you get a video player. There's no flash of unstyled content. If you clone it and insert the copy into the DOM again, all of the playback controls are still wired up and responsive. I'm proposing we add something that lets script extend the set of tag names, so there is less of a bright line between elements defined in the HTML spec and elements defined in script. Something like: HTMLElement.register('x-contacts', ContactPicker); The first argument is an element name. The second is a constructor function, whose prototype property points to an object which introduces the API for contacts (eg a refresh method) and is wired up to HTMLElement.prototype. When the parser encounters <x-contacts/>, it creates an object by calling the ContactPicker function as a constructor. The constructor can wire up any internal state it wants, like creating DOM and attaching event listeners. When a script later retrieves the element, say with document.querySelector('x-contacts'), the object created earlier with ContactPicker is what it gets back. Since the constructor is run as the parser is creating the element, there's no time when the unenhanced element is available to script. My proposal pretty much ends here: The component itself is just implemented with script, DOM and CSS. There are no special hooks for participating in layout (just use DOM and CSS) or form submission (just create form controls using DOM) or security (same origin policy still applies) or networking (just use WebSockets or XHR) or anything else. There is no magic, other than teaching the browser about the new tag name. I'm suggesting that all registered tag names have to start with x-. This is to give component authors a place to build without worrying about conflicting with future HTML specs; the HTML spec won't define elements with names starting with x-. (Component authors may chose x- names that conflict with each other, though.) People working on the HTML spec can study what kinds of components are popular in the wild and consolidate the best ideas into new HTML elements. Page authors can decide if and when to move their pages from their bespoke x- components to the HTML alternatives, which should be better. Pages that have to work in older browsers can use fallback content. (This is exactly what the HTML spec advises authors to do with the video element, incidentally.) In newer browsers the component script can remove or hide the fallback content. There's the question of what to do when there is a call to HTMLElement.register('x-contacts', …) at some point after an x-contact element has been parsed. We have to do something that makes sense for async scripts. I think the solution is to replace the element, and the recent discussion on this list about renameNode is interesting and relevant. One practical issue with this proposal is that creating a subtype of HTMLElement in script (ie defining the ContactPicker function in the above example) is hard/impossible. A couple of the problems are: - Defining a subtypes in JavaScript is hard to begin with: You need to create a function, and maybe set its object prototype up; you need to create a prototype object and wire its object prototype up; you might add a constructor property to the new prototype object; and you need to remember to call your "base type" constructor to let it do any setup it requires. - Even if you do all this, HTMLElement.call(this), which you would use to initialize your "base" type, doesn't work in the browsers I tried: If HTMLElement even has a call method, it raises an exception if you try to use it. For now, I think we need something that (a) is implementable in browsers today and (b) is harmonized with where JavaScript is going in TC39 and where DOM bindings are going ala Web IDL. The root cause of most implementation difficulties I see (I mostly looked at WebKit) is that when a JavaScript wrapper object is created, the browser wants to allocate a special wrapper object. It can't know to do this in advance in the general subtyping case. So I suggest we limit component authors to an "init" method that can create child DOM and register event listeners. This is not unlike XBL's enteredDocument, YUI's renderUI/bindUI, etc. The browser can provide a factory that mints constructors that call this init method at the appropriate time, but meet the browser's idiosyncratic constraints of how wrapper object construction unfolds. Using it would look like this: function initContacts(element) { // Set up child DOM // Add event listeners } var ContactPicker = HTMLElement.extend({init: initContacts}); The resulting function can be used with HTMLElement.register, or even called directly: HTMLElement.register('x-contacts', ContactPicker); document.body.appendChild(document.createElement('x-contacts')); // works document.body.appendChild(new ContactPicker()); // also works You will notice that this says nothing about how prototypes are wired up. It should. Maybe the argument to extend should have an optional second field, proto, that specifies the new methods/getters/setters that ContactPicker's prototype should introduce. This is not a general subtyping mechanism! It is only designed for setting up subtypes of HTMLElement for use with register. When ECMAScript and the DOM bindings are sufficiently aligned, HTMLElement.register can be opened up to accept constructors defined using ordinary ECMAScript mechanisms for subtyping DOM interfaces. Scripts can continue to use extend (it is pretty succinct) or constructors set up their own way. Lastly, I wanted to mention how this relates to the component model. This is one small part. It could be used with the other parts--a component constructor could set up its presentation aspects in shadow DOM, for example. It could also be used by itself. As I illustrated above it eliminates a lot of the difficulties of working with widgets today. Thanks for reading this far! These proposals aren't formal or detailed. I would love to get feedback as I try to nail down some specifics.
Received on Wednesday, 31 August 2011 05:34:10 UTC