Puzzle: Define HTML custom element subclasses that can fill in base class insertion points

This post presents a little web component architectural puzzle which I’ve come across in the early stages of creating Quetzal. The puzzle deals with how Quetzal should best deliver an important component service on an HTML custom element substrate, and relates specifically to subclassing semantics. Any suggestions or comments would be much appreciated.

Background on the puzzle

Quetzal is an attempt to deliver key features of the QuickUI component model in HTML custom elements. One such feature is that an element subclass should be able to easily populate a slot (insertion points, in HTML parlance) defined by a base class. In practice, there are many situations in which you want to be able to say, “This new UI component should be just like that existing UI component, only with some stuff pre-filled in.” For example:

  • The QuickUI documentation presents a simple page template example in which classes in a small page template hierarchy fill in specific bits of their parents classes.
  • A DateComboBox fills in the popup portion of a ComboBox, which in turn is filling the content portion of a PopupSource. This same facility is also used throughout the QuickUI Catalog. Moreover, it is used in many QuickUI apps in which a stock Catalog component is specialized for the app’s context.
  • Along those same lines, this same issue should crop up in any organization that tries to create a library of standard components which implement the organization’s visual design language. Suppose your site’s designer has created a cool button class as an HTML custom element, and you have used that to create an Add to Cart button. You write some script so the button can show inside the button the existing number of items in a customer’s online shopping cart (to the right of the button label, say). You now want to package up the Add to Cart button so that it can be used as a component in its own right. For flexibility, you want the button’s text label to vary in places.

Well-defined subclassing semantics are essential for creating a UI component library with a good separation of concerns. If you look at the class hierarchy depicted for DateComboBox (above), you’ll get a sense of the degree to which it’s possible to portion out specific roles to a small constellation of classes, such that each class can focus on just doing one thing really well.

I’m hoping that it is possible to take advantage of such subclassing semantics in HTML custom elements — but it’s not proving to be particularly easy.

The puzzle

The puzzle is to come up with an architecture for custom element subclasses that meets the following design criteria:

  1. An instance of a subclass is a proper instance of its base class. All the normal JavaScript stuff should work: property/method access should go up the prototype chain, and a subclass instance should report that it is an “instanceof” the base class. By default, the HTML <element> syntax permits an “extends” attribute to identify a base class, but a purely script-based solution that sets up the class hierarchy correctly is equally valid.
  2. A subclass can put stuff into an insertion point defined by the base class. That is, the subclass can fill in a slot (or slots) defined by a base class. In turn, the subclass should be able to redefine such an insertion point so that the subclass itself can be subclassed.
  3. Unless overridden, all base class behavior should function properly in an instance of the subclass. E.g., if the base class wires up an event handler, then this works as expected for subclass instances too.
  4. Base class properties/methods can be overridden by the subclass. A subclass’ property/method implementation should be able to invoke the base class’ implementation by whatever language means are necessary. (CoffeeScript provides sugar for this; plain JavaScript developers have alternate ways of achieving the same result.)
  5. The base class can be any HTML custom element class; the base class author shouldn’t have to do special work a priori to enable this kind of subclassing. This ensures a Quetzal author can always use someone else’s element class as a base class — even if that other person has never heard of Quetzal.

A successful solution needs to meet all five of these criteria. So far, the approaches I’ve tried can satisfy at most four at a time.

Example

Let’s walk through a example from the small set of custom elements currently shown on the Quetzal home page. This set includes a base element class called quetzal-button that shows its content inside a button, and another element class called icon-button which adds an icon to the plain-button content. For clarity, here let’s just call that base class plain-button instead of quetzal-button, since the following source won’t actually involve Quetzal. In any event, we want markup like this:

<plain-button>Plain button</plain-button>
<icon-button icon="document.png">Icon button</icon-button>

… to produce something like this:

Buttons

Where icon-button is reusing all the styling and behavior from plain-button; it’s not duplicating the styling and behavior. The challenge is to create the icon-button element so it both inherits (in the class sense) from plain-button and extends the visual representation of plain-button.

A partial solution to filling in base class insertion points

The first challenge is: how can icon-button add elements to the content shown by plain-button? Some approaches:

  1. We could try to apply the template for both plain-button and icon-button to the same host element. The Shadow DOM spec supports multiple shadow trees attached to the same host. This feature alone is insufficient for the above example. Unless one shadow tree takes care to incorporate the other somehow, the most recently-added shadow subtree wins. If plain-button renders last, we get a button but no icon; if icon-button renders last, we get an icon but no button.
  2. We can include a <shadow> element in the template for icon-button, and ensure plain-button renders its shadow subtree first. The <shadow> element allows icon-button to effectively include the representation for plain-button. Unfortunately, this inclusion effectively wraps the base representation, rather than filling it in. An icon-button does get a button and an icon, but the icon and content render outside an empty button: shadow
  3. We can have icon-button create an instance of its own base class, then have that instance contain the icon and the icon-button’s own content. (This approach is based on a suggestion from Shadow DOM spec author Dimitri Glazkov.)

Approach #3 does what we want from a strictly visual perspective. (Behavior is a separate matter.) The source for a Polymer element version of this approach looks something like:

<element name="plain-button" extends="button">
    <template>
        <button>
            <content></content>
        </button>
    </template>
    …
</element>

<element name="icon-button" extends="plain-button" attributes="icon">
    <template>
        <plain-button>
            <img src="{{icon}}">
            <content></content>
        </plain-button>
    </template>
    …
</element>

See a live example of this. It should work in most browsers, but use Chrome if you want to see it working with real Shadow DOM. (Note: a Chrome bug prevents the buttons from responding correctly to mouse interactions. The buttons do receive mouse events, but for now it just doesn’t look that way.)

If you open the example and inspect it in Chrome dev tools, you’ll see that an icon-button has a shadow subtree containing a plain-button; the plain-button contains its own shadow subtree. Content of an <icon-button> element is therefore distributed twice: once into the <plain-button> element, and then again into the native <button> element. The ability of Shadow DOM to distribute nodes multiple times is called reprojection. (Or, at least, it meets the definition of reprojection as I understand it: “when an insertion point is a child node of another shadow host”.)

Inheritance versus containment

Unfortunately, while this approach looks right, it doesn’t behave quite right. An icon-button here is not only an instance of plain-button, it also contains a plain-button. That’s problematic.

  1. When icon-button instantiates its inner plain-button, the inner button has no way to know its relationship to icon-button. Among other things, this means icon-button can’t easily override behavior defined by plain-button (one of the design criteria above). In the JS Bin example, you can turn on the Console pane to see debug output. The plain-button element class defines a readyCallback (in Polymer, “ready”) that invokes a base class method called log(). The icon-button class overrides that log() method, but because the inner plain-button is just that — a plain-button — its readyCallback will invoke the base plain-button implementation of log() instead of icon-button’s specialized log() implementation. Running the demo invokes log() four times, when: 1) creating an instance of plain-button to use as the prototype for icon-button, 2) creating the visible plain-button with text “Plain button”, 3) creating the inner plain-button used by the visible icon-button, and 4) creating the visible icon-button with text “Icon button”. It’s #3 and #4 together that are the problem: what we really wanted to do is invoke icon-button’s log() implementation once.
  2. Automatic element references (a la Polymer, and also in Quetzal) aren’t inherited by default. If plain-button defines an element with id #foo, then plain-button methods can access that element via the automatic reference this.$.foo. Similarly, we want an icon-button to have access to the same reference this.$.foo defined by the base class. (Or, at least, we can debate whether such automatic references should be treated as “private” or “protected”, but it seems to me that “protected” would be useful.) It’s possible to work around this particular issue for a known set of frameworks — that is, Quetzal could workaround this problem for its own classes, and perhaps for those defined by Polymer — but it wouldn’t work in the general case of an unknown framework.
  3. It’s easy to end up in situations where both icon-button and plain-button are duplicating work. Suppose an icon-button method invokes a super-method of the same name defined by plain-button, and suppose the base implementation of that method performs expensive work or obtains a reference to some resource. When the inner plain-button is instantiated, it might do that work — and then the same work or allocation might be repeated by the outer icon-button when it invokes the super-method. Conventions could be established to avoid this, but it would complicate otherwise simple situations, and again make it hard to use subclass elements from other frameworks.
  4. As a common case of the above point, if the inner plain-button wires up an event handler, then it’s easy to end up in situations where the event is bound by both the inner plain-button and the outer icon-button. If the event bubbles up from something inside plain-button (a click on the button, say), you would end up handling the same event twice.

We could try to simplify things by just containing an plain-button, and not deriving from it. This forces us to give up one of the original design criteria outlined above: an instance of icon-button wouldn’t actually appear to be an “instanceof” plain-button. Moreover, if plain-button defined some attributes (“disabled”, say), icon-button would have to explicitly handle those too and forward their implementation to the inner plain-button.

We could have icon-button create a placeholder element (a <div>, say), create a shadow root for it, and populate that root with a copy of plain-button’s template but without actually instantiating that inner element as a real, live plain-button. This is the approach that Quetzal currently uses. It solves a number of problems, but is dependent on knowing how a given base element class works. Quetzal reaches into the base class’ implementation to obtain its template and then clones it, which might not be possible with other frameworks. This violates one of the design criteria above.

We could create a temporary instance of plain-button elsewhere, then clone just its contents into the icon-button instance. This avoids requiring detailed knowledge of what the base class is doing. But it could also result in subtle problems. E.g., the base class might not be expecting to have to serialize all its state into its shadow subtree, in which case the cloned content might not represent a coherent instance of the base class.

Looking for suggestions

This post is effectively a form of rubber duck debugging. The simple act of writing this up has forced me to better understand the problem, and led to consideration of alternate lines of attack. The puzzle remains unsolved, however. Given my understanding of custom elements, and the design criteria for the puzzle above, I’m not sure whether a solution exists.

It’s theoretically possible I’ve hit some limit in the expressiveness permitted to custom elements in their current state. Perhaps that limitation could be addressed. If not, I’d  have to write off a big chunk of the solutions used by the QuickUI Catalog and QuickUI apps, and find alternate ways of meeting the same needs.

I’m hoping, however, that I’m just missing something. If you have some passing familiarity with HTML custom elements and Shadow DOM, and have ideas about how to approach this problem within the existing technology, I’d love to hear them!

Advertisement

Do web component developers still need jQuery?

I love you, jQuery. But maybe it’s time we started spending some time apart.

Improvements in cross-browser feature set and compatibility are reducing the need for jQuery, but more importantly, it turns out that a component-based app needs very little jQuery to begin with. These insights led to one of the first decisions I hit in starting Quetzal: should it require jQuery? The answer, so far, is no.

Since its inception in 2007, QuickUI has relied on jQuery as a crucial browser abstraction layer. Without jQuery, QuickUI would have never have come to exist. However, the QuickUI runtime itself only uses a fairly small set of core jQuery functions, and modern browsers now deliver standards-based solutions for those situations. Moreover, I’ve noticed that, when building QuickUI component-based apps, component code tends to rarely require sophisticated jQuery.

Meanwhile, the web has moved forward. The modern browsers — the latest Chrome, Safari, Mozilla, and Internet Explorer 10+ — are all much, much more consistent to develop for than browsers were six years ago. Glancing at pages on QuirksMode, one generally sees a see of green compliance, with most of the red markers of non-compliance for older versions of IE. And according to StatCounter, market shares for both IE 8 and 9 have now dropped below 10% (each), so it’s plausible for mainstream organizations to justify ignoring them.

Let’s break down the things jQuery is good at, and consider how necessary are in a web component-based application (specifically) targeting the modern browsers:

  • Handling an array of DOM objects. It’s convenient to be able to apply a jQuery function or plugin to a jQuery object containing a collection of DOM elements. However, modern web languages (CoffeeScript, ES6, etc.) provide syntax for iterating over a collection, substantially reducing the need for a special library for this purpose. And, while jQuery’s array model is easy to consume, it also makes writing components harder. QuickUI control classes inherit from jQuery, which means every QuickUI class method has to consider the possibility it’s being applied to multiple objects at a time, not just one. In practice, that’s been a consistent source of complexity and bugs.
  • Selectors. The standard querySelector and querySelectorAll functions provide a reasonable way to find matching elements. (I believe those functions were at least partially inspired by jQuery.) I’ve heard that jQuery’s Sizzle engine provides more power than querySelector, but again, in a component-based app, you’re rarely doing sophisticated searching of the DOM. In practice, when I’m writing an app with QuickUI components, I do very, very little searching of the DOM. Every QuickUI component already has its own reference to exactly the sub-elements it cares about. Polymer does something similar with automatic node finding. With such facilities in place, there’s no need to grovel around in the DOM to find something you’re looking for. In fact, with Shadow DOM, the things you’re probably looking for aren’t even findable. This is a good thing; encapsulation is preventing you from writing brittle code. Every time you do a $(“#foo”), you’re running the risk of picking up the wrong #foo — maybe not today, but tomorrow, when someone else adds a #foo element somewhere on the page. In Quetzal or Polymer, you never search for something like that; a component already has a direct reference (this.$.foo) to the element you want to manipulate. Unlike a DOM search, dereferencing is instantaneous and 100% reliable.
  • Traversing. Ditto. In practice, component subtrees  — that is, the set of elements managed directly by a component — just aren’t that deep. A deep subtree is, in fact, often an indicator that component refactoring is in order. It’s exactly analogous to the way a deeply-nested set of code blocks (conditionals, loops) within a single function usually indicates the function should be refactored. Nearly all the time a QuickUI component needs to traverse the DOM, it wants to iterate over its own set of children — which, as noted above, can now be easily done in a modern language with decent syntax.
  • DOM manipulation. As browsers have become more consistent in the semantics of DOM operations, jQuery feels less necessary here. And much of the jQuery DOM manipulations one sees are a means to set up all or part of the DOM. in jQuery, one often sees code like: “Find all the divs of class ‘.menuItem’, and wrap them, stuff them, and wire them up so that they turn into menu items.” The existence of web components provides a much better way to accomplish the same result. All the population can be done through custom elements that provide a template for their DOM. That said, jQuery does provide a useful collection of helper functions. For example, to me it feels easier to use jQuery’s css() method, which can take a JavaScript object as a parameter, than use the raw DOM “style” property directly. Generally speaking, the DOM API feels more like an old school C API, while jQuery feels like a JavaScript API.
  • Function chaining. jQuery chaining lets you concisely apply a set of selector, traversal, and manipulation operations. In practice, all three of those types of operations come up less often in a web component-based app. In particular, one often sees long chains of jQuery function calls when populating the DOM, but a <template> is a cleaner way to do that declaratively. Over the years, in QuickUI apps, I’ve noticed that I use jQuery chaining less and less often, to the point where I only rarely take advantage of it today.
  • Events. Microsoft IE 9 finally added support for addEventListener, so that it’s possible to wire up event handlers in a consistent way. I expect there are still many discrepancies lurking in the details — when each browser decides to fire an event, for example — that might prove tricky to work around without an abstraction layer like jQuery that can normalize behavior.
  • Effects. CSS transitions and transition events now provide an easy, cross-browser way to do many of the same effects jQuery was first noted for. For years, the jQuery home page used to have a simple demo which, when you clicked a button, made a new DOM element appear with a transition effect. Such effects are now easily achievable without jQuery.
  • Data. jQuery provided a useful $.data service to associate data with DOM elements, because a browser’s garbage collector can get confused when DOM elements and JavaScript objects reference each other. The various browsers also had myriad bugs and inconsistencies with regard to extending DOM elements. So UI framework developers like those on Prototype gave up on extending the DOM. However, as far as I can tell from the way Polymer is tackling things, extending the DOM now appears to work (generally) as expected. So perhaps $.data is no longer necessary.
  • Ajax. I don’t write a ton of Ajax code myself, so for argument’s sake, let’s stipulate that jQuery’s Ajax wrappers are handier than directly working with XMLHttpRequest. In particular, jQuery’s use of promises as a data type simplify the task of writing async code. Perhaps for the time being, this particular aspect of jQuery is worth using on its own. A proposed browser standard for futures may reduce that benefit, however.
  • Plugins. Many, many of the jQuery plugins that exist today essentially create component-like things. These plugins effectively constitute a DOM template with some packaged behavior. I’d argue that a web component is a clearer, more maintainable way to achieve the same result. Encapsulation, in particular, is a huge advance to providing robust components. Moreover, many other uses for plugins could now be achieved by extending DOM elements directly.

Generally speaking, in a component-based app, you want to give each component the responsibility for managing its own appearance and behavior. You don’t want code walking all over the DOM tree and mucking around with things that aren’t its direct responsibility. Instead, you talk to the component managing the part of the DOM you care about, and ask that component to manipulate the elements it directly owns. In classic object-oriented programming terms, this is an application of the Law of Demeter.

In practice, compartmentalizing things that way leaves each component doing very simple manipulations on a comparatively small set of elements: instantiating a new element; iterating over its own children; applying or removing a style; etc. If a component wants to do something more sophisticated to its internals, more likely than not the component should delegate that operation to one of its own sub-components. The component’s own need to search is limited, reducing the need for a powerful selector/traversal engine. And, given a reasonably good programming language, simple DOM manipulations can be performed effectively — and with better performance — by directly accessing the DOM API.

I’m no cross-browser DOM API expert, and I’ve only just started to try to do things without jQuery. I could easily hit a landmine tomorrow, tripping upon some cross-browser nastiness I’ve been blithely unaware of, which jQuery for years has been invisibly and reliably been protecting me from. That said, work on Quetzal is progressing fairly smoothly without jQuery, and a week or two into this project, I’m not missing most of jQuery. It would be nice to have a much smaller library of helper functions which present a more JavaScript-flavored approach to the DOM API, as in jQuery’s css(), mentioned above.

From one standpoint, you could say I’ve just traded one browser abstraction layer (jQuery) for another (Polymer’s platform.js). However, platform.js feels like a pretty different animal than jQuery:

  • The beauty of Polymer’s approach of polyfilling forthcoming web standards in older browsers is that you can write code against the abstraction layer today that should invisibly start working against native implementations where and when those exist. It’s still early, and that promise remains to be proven — but that’s a very compelling promise.
  • Those future standards mean you’re working with facilities that someday every other developer will have (whether they want them or not). You’re writing code on top of a library that could get smaller over time, not larger.
  • The other difference with platform.js, as far as today’s web component developers are concerned, is that it’s really the only game in town. Unless you have the luxury of targeting the latest release of Chrome, you’ll need to use platform.js (or the complete polymer.js) to run on other browsers.
  • If one’s goal is to write components that many people will use, it makes sense to reduce the number of additional dependencies. If jQuery’s not required, then the lack of that dependency to some degree facilitates sharing.

We’ll see how the Quetzal experiment evolves, but so far, writing directly to the DOM API is working out okay.

So, jQuery, maybe we should spend some time apart. Maybe we should see some other people. It’s not you — it’s me! Don’t worry; I might miss you terribly and come running back. Or maybe we’ll just be friends.

It’s okay. We’ll always have IE6.

Quetzal: an experimental translation of the QuickUI component model to HTML custom elements

I’ve started an experiment called Quetzal that considers translating the core concepts from QuickUI to the proposed web component standards currently embodied by Polymer.

[Update June 13, 2014: The Quetzal project has grown into an open source project called Basic Web Components. Please take a look!]

While continuing working on QuickUI, I’ve been tracking the progress of the Google-led efforts on web components: custom elements, Shadow DOM, and related advances in the web platform. Those technologies address many of the same issues QuickUI addresses, so I’ve been trying to chart a path by which QuickUI and web components could co-evolve.

Until recently, those web component technologies were generally available only on Google Chrome, which means QuickUI can’t rely on them. However, since the start of the year I’ve been watching Google’s Polymer project which, among other things, offers a suite of polyfills that allows one to create and use components in the other “modern” browsers, including recent versions of Safari, Firefox, and Internet Explorer 10+. Polymer offers a fairly compelling story for using those future browser technologies today.

I’ve spent a bit of time looking at how to retrofit support for Shadow DOM and other aspects of web components into QuickUI. While that’s led to some progress, I’m not entirely sure that that’s the best approach. To ensure this is being done the best way, I’d like to try an alternative approach build from scratch directly on top of a custom element substrate. That experiment is Quetzal.

There are a number of aspects of QuickUI that I believe are quite compelling, and which are either not easily supported in the proposed web standards, or appear under-represented in the current body of web components work. My goal in Quetzal is to explore whether those aspects of QuickUI have meaning in the world of web components, and what’s the best way to bring those benefits forward. Some of those aspects of QuickUI I would very much like to see carried forward to the world of custom elements are:

  • A focus on subclassing as a means to achieve well-factored code, including a good separation of concerns. This includes an approach to populating the DOM in which an element class can fill in properties and content slots defined by their base classes.
  • The ability to concisely define component appearance and behavior in script instead of markup. While the <element> syntax is part of the standard, and therefore a useful baseline, markup feels limiting compared to what’s possible in script. A compact JavaScript object format can be at least as expressive, and possibly more expressive, than HTML.
  • A convention for multiple, named, DOM-valued properties.
  • The ability to run code when an element’s contents change.
  • Syntactic sugar for quickly defining common types of component properties.
  • Helper functions for tasks that come up often in UI component design. This includes, for example, a lightweight model by which an element can respond to changes in its size in order to perform custom layout.
  • A significant library of well-designed web user interface components, including a large number of useful base classes that people can use directly as the starting point for their own work.

The Quetzal experiment seeks to preserve the above features, while still allowing a designer or developer to create new custom elements which can interoperate with custom elements created by any other means (e.g., as Polymer elements).

Some early technical decisions for Quetzal:

  • I’m leaving jQuery out of Quetzal. I’ll go into that decision in more detail later, but the bottom line is that jQuery no longer seems absolutely necessary for web development. When QuickUI began back in 2007, jQuery was a vital cross-browser abstraction layer, but browsers have become a lot more consistent in the intervening years. If you look at the browsers currently supported by Polymer, the core DOM API appears consistent enough that it’s not overly cumbersome to use directly. And while any custom element library should support jQuery use, it would be nice if jQuery weren’t a requirement.
  • For the time being, Quetzal is written in CoffeeScript rather than plain JavaScript. I find CoffeeScript much more expressive, more productive, and easier to think in than plain JavaScript. As with QuickUI (which is also written in CoffeeScript), Quetzal elements can of course be created and extended in plain JavaScript. Still, I recognize that using CoffeeScript limits one’s audience. If Quetzal were to evolve to be a real open source project, I might feel the need to back-port it to JavaScript.
  • Quetzal only relies on the lower-level platform.js library created by the Polymer project, rather than the higher-level polymer.js library or the (higher still) library of Polymer elements. The Polymer home page currently includes an architectural diagram illustrating the relationship between these two libraries. Quetzal builds on the lowest, red-colored platform.js level, not the higher yellow or green levels. In this regard, Quetzal is comparable to Polymer elements. Because both rely on web standards, the results should be easily interoperable. As a side effect, Quetzal should also help prove out the ability of someone other than Google to build a UI component framework on top of platform.js.
  • I’m currently working and testing primarily in Chrome. At various points, I check to make sure Polymer is polyfilling everything correctly under other browsers, but at this early stage, it’s likely stuff will appear wonky in other browsers.

Quetzal isn’t ready for real use yet: it does just a few things at this stage, it’s not document, it’s buggy, it doesn’t work cross-browser (even with polyfilled custom elements), etc. But I wanted to announce the experiment now so that I can follow up here with additional posts as I go along. Work on Quetzal is generating questions I want to ask others, and to provide context for those things it’s going to be helpful to be able to reference Quetzal posts here and source code on GitHub. After exploring some ideas, Quetzal’s useful life may come to an end, or its lessons might get folded back into QuickUI, or it may evolve into a library of Polymer elements.

If you’re interested in following along, subscribe to this blog, and/or follow me on Twitter and Google+.

Cheers,
Jan Miksovsky