Re: Component Models and Encapsulation (was Re: Component Model: Landing Experimental Shadow DOM API in WebKit)

Maciej, as promised on #whatwg, here's a more thorough review of your
proposal. I am in agreement in the first parts of your email, so I am
going to skip those.

> == Are there other limitations created by the lack of encapsulation? ==
>
> My understanding is yes, there are some serious limitations:
>
> (1) It won't be possible (according to Dmitri) to attach a binding to an object that has a native shadow DOM in the implementation (e.g. form controls). That's because there can only be one shadow root, and form controls have already used it internally and made it private. This seems like a huge limitation. The ability to attach bindings/components to form elements is potentially a huge win - authors can use the correct semantic element instead of div soup, but still have the total control over look and feel from a custom script-based implementation.
>
> (2) Attaching more than one binding with this approach is a huge hazard. You'll either inadvertently blow away the previous, or won't be able to attach more than one, or if your coding is sloppy, may end up mangling both of them.
>
> I think these two limitations are intrinsic to the approach, not incidental.

I would like to frame this problem as "multiple-vs-single shadow tree
per element".

Encapsulation is achievable with single shadow tree per element by
removing access via webkitShadow. You can discover whether a tree
exists (by the fact that an exception is thrown when you attempt to
set webkitShadow), but that's hardly breaking encapsulation.

The issues you've described above are indeed real -- if you view
adding new behavior to elements a process of "binding", that is
something added to existing elements, possibly more than once. If we
decide that this the correct way to view attaching behavior, we
definitely need to fix this.

I attempted to articulate a different view here
http://lists.w3.org/Archives/Public/public-webapps/2011JanMar/0941.html.
Here, adding new behavior to elements means creating a sub-class of an
element. This should be a very familiar programming concept, probably
more understood than the decorator or mixin-like "binding" approach.

For the key use case of UI widgets, sub-classing is very natural. I
take a div, and sub-class it into a hovercard
(http://blog.twitter.com/2010/02/flying-around-with-hovercards.html).
I rarely bind a hovercard behavior to some random element -- not just
because I typically don't need to, but also because I expect a certain
behavior from the base element from which to build on. Binding a
hovercard to an element that doesn't display its children (like img or
input) is useless, since I want to append child nodes to display that
user info.

I could then make superhovercard by extending the hovercard. The
single shadow DOM tree works perfectly in this case, because you
either:
1) inherit the tree of the subclass and add behavior;
2) override it.

In cases where you truly need a decorator, use composition. Once we
have the basics going, we may contemplate concepts like <inherited>
(http://dev.w3.org/2006/xbl2/#the-inherited-element) to make
sub-classing more convenient.

Sub-classing as a programming model is well-understood, and easy to grasp.

On the other hand, the decorators are less known and certainly carry
hidden pains. How do you resolve API conflicts (two bindings have two
properties/functions by the same name)? As a developer, how do you
ensure a stable order of bindings (bindings competing for the z-index
and depending on the order of they are initialized, for example)?

>
>
> == OK Mr. Fancypants, do you have a better proposal? ==
>
> I haven't thought deeply about this, but here's a sketch of a component model that is just as trivial to use and implement as what is proposed, yet provides true encapsulation. It sticks with the idea that the only way to create a component DOM is programmatically. But it immediately provides some advantages that I'll expand on after throwing out the IDL:
>
> interface Component {
>    // deliberately left blank
> }
>
> interface TreeScope : Node
>  {
>    readonly TreeScope parentTreeScope;
>    Element getElementById (in DOMString elementId);
> }
>
>  interface BindingRoot : TreeScope {
>   attribute bool applyAuthorSheets;
>   readonly attribute Element bindingHost;
>  };
>
> [Callback]
> interface ComponentInitializer {
>  void initialize(in BindingRoot binding);
> };
>
>  partial interface Document {
>     Component createComponent(in Node template, in ComponentInitializer initializer);
>  };
>
> partial interface Element {
>    bindComponent(in Component component);
>    unbindComponent(in Component component);
> }
>
> The way this works is as follows:
>
> (1) The component provider creates a DOM tree that provides templates for bindings that instantiate that component.
> (2) The component provider also makes an init function (represented above as a callback interface) which is called whenever an instance of the component is bound (see below).
> (3) The component provider calls createComponent with these things and gets back a Component object, which is completely opaque and exposes no implementation details (though if we wanted we could add some visible metadata attributes like a name or what have you) but can be bound to an element.
> (4) The component provider hands back this token to its client.
> (5) The client can instantiate the component as many times as it wants by binding it to one or more elements.
> (6) When Element.bindComponent is called, it creates the BindingRoot, clones the template into it as children (it can be a DocumentFragment if desired), attaches it to the element, and then calls the initializer function on the newly created binding root, so that it can set up event listeners and such.
>
>
> Notice that this scheme is not significantly more complex to use, spec or implement than the shadow/shadowHost proposal. And it provides a number of advantages:
>
> A) True encapsulation is possible, indeed it is the easy default path. The component provider has to go out of its way to deliberately break encapsulation, though of course it can if it wants to.

Yup.

> B) Binding is atomic - the shadow root is not built up incrementally so you can't have a half-built binding.

I am not sure what's the danger here. Are you trying to protect the
author from displaying the shadow subtree while it's still being
built?

> C) Has a natural extension to allowing more than one binding on an element, having a stacking behavior like XBL.

Given discussion above, I don't see this as an advantage.

> D) Can readily support custom components bound to form controls, with either semantics of stacking on top of the native binding or replacing it.

This is where override and compose approaches work well for
inheritance-based approach.

> E) Avoids use of the jargon-ish, mysterious and potentially confusing term "shadow" in favor of "binding" and "component" which are much more clear IMO.

I agree that "shadow" is jargon-ish. I would love to have a better
name for it that's not scary. I like "component" terminology!

> F) The code to build up the DOM for the binding with raw DOM calls only has to run once. After that it just gets cloned, which is likely faster than creating a whole fresh one with raw DOM calls.

Right. This is good stuff.

> G) Naturally extensible to other ways of creating components, perhaps on a declarative template a la XBL2.

Ditto.

Overall, aside from multiple trees and the use of bindings rather than
object inheritance, I like this API. Without those two, it's exactly a
superset of the my proposal, minus webkitShadow. I am a huge fan of
being able to produce a Component object that acts like a DOM object.

To make further progress, I would like to concentrate on resolving
these two issues:

1) should we use object inheritance (one shadow subtree) or mixins
(multiple shadow subtrees)?
2) do we need webkitShadow or similar accessor to shadow subtree(s)?

I think these are all resolved by supplying use cases and rationale. Right?

:DG<

Received on Thursday, 30 June 2011 20:04:00 UTC