W3C home > Mailing lists > Public > public-webapps@w3.org > April to June 2014

Re: Separating Transclusion Mechanisms for Inheritance and Data Binding

From: Dimitri Glazkov <dglazkov@chromium.org>
Date: Tue, 22 Apr 2014 10:22:23 -0700
Message-ID: <CADh5Ky1-93WOJYA7wEyqiRXtJQXi3f5z+KyyK5b5+-y_CJSxeg@mail.gmail.com>
To: Ryosuke Niwa <rniwa@apple.com>
Cc: public-webapps <public-webapps@w3.org>
On Thu, Apr 17, 2014 at 2:42 AM, Ryosuke Niwa <rniwa@apple.com> wrote:

> *Review: Template Inheritance in the Current Specification*
>
> In the current specification, a super class doesn't define any hooks for
> subclasses.  Instead, it defines insertion points into which nodes from the
> original DOM ("light DOM") is inserted, and then subclasses use shadow
> element to replace elements that get distributed into superclass's
> insertion points.
>
> Consider my-card element used as follows:
>   <my-card>
>   <span class="name">Ryosuke Niwa</span>
>   <span class="email">rniwa@apple.com</span>
>   </my-card>
>
> Suppose this element's shadow DOM looks like this:
> Name: <content select=".name"></content>
> Email: <content select=".email"></content>
>
> Then in the composed tree, the first span is distributed into the first
> content element and the second span is distributed into the second content
> element as follows:
>   <my-card>
>     <!-- shadow root begin -->
>     Name: <content select=".name">
>       <!-- distribution begin -->
>       <span class="name">Ryosuke Niwa</span>
>       <!-- distribution end -->
>     </content>
>     Email: <content select=".email">
>       <!-- distribution begin -->
>       <span class="email">rniwa@apple.com</span>
>       <!-- distribution end -->
>       </content>
>     <!-- shadow root end -->
>   </my-card>
>
> If I had my-webkitten-card that always as "WebKitten" as a name that
> inherits from my-card, its shadow DOM may look like this:
>   <shadow>
>     <span class="name">WebKitten</span>
>     <content></content>
>     <span class="email">kitten@webkit.org</span>
>   </shadow>
>
> If I had an instance of my-webkitten-card as follows:
>   <my-webkitten-card>
>   <span class="name">Ryosuke Niwa</span>
>   <span class="email">rniwa@webkit.org</span>
>   </my-webkitten-card>
>
> Then its composed tree will look like this:
>   <my-webkitten-card>
>     <!-- my-webkitten-card's shadow root begin -->
>       <shadow>
>       <!-- my-card's shadow root begin -->
>       Name: <content select=".name">
>         <!-- distribution begin -->
>         <span class="name">WebKitten</span>
>         <span class="name">Ryosuke Niwa</span>
>         <!-- distribution end -->
>       </content>
>       Email: <content select=".email">
>         <!-- distribution begin -->
>         <span class="email">rniwa@webkit.org</span>
>         <span class="email">kitten@webkit.org</span>
>         <!-- distribution end -->
>       </content>
>       <!-- my-card's shadow root end -->
>     </shadow>
>     <!-- my-webkitten-card's shadow root end -->
>   </my-webkitten-card>
>
> Here, my-card's shadow DOM was inserted into where the shadow element
> existed in my-webkitten-card's shadow DOM, and the insertion points inside
> my-card's shadow DOM got nodes distributed from shadow element's children
> including nodes inside content element.  If we didn't have the content
> element inside my-webkitten-card with "name" and "email" classes, then we
> would only see WebKitten and kitten@webkit.org distributed into my-card's
> insertion points as in:
>
>   <my-webkitten-card>
>     <!-- my-webkitten-card's shadow root begin -->
>     <shadow>
>       <!-- my-card's shadow root begin -->
>       Name:
>       <content select=".name">
>         <!-- distribution begin -->
>           <span class="name">WebKitten</span>
>         <!-- distribution end -->
>       </content>
>       Email:
>       <content select=".email">
>         <!-- distribution begin -->
>           <span class="email">kitten@webkit.org</span>
>         <!-- distribution end -->
>       </content>
>       <!-- my-card's shadow root end -->
>     </shadow>
>     <!-- my-webkitten-card's shadow root end -->
>   </my-webkitten-card>
>
> *Separating Transclusion Mechanisms for Inheritance and Data Binding*
>
> The current model mixes data binding and inheritance if we consider
> distributing nodes from the "light DOM" as a form of data binding.  Namely,
> distributing nodes from my-card's or my-webkitten-card's light DOM is data
> binding where the data model is DOM whereas distributing nodes from
> my-webkitten-card's shadow element into my-card's insertion points is an
> inheritance hook.
>
> Furthermore, the transclusion mechanism for inheritance happens backwards.
>  Instead of a superclass defining a transclusion points for its subclasses
> to use, the subclasses are overriding the meaning of insertion points in
> the superclass by injecting nodes.  This is how existing JS libraries and
> frameworks do template inheritance.
>
> For example, the following two JS template libraries that support
> inheritance both allow superclass template to define "named blocks" that
> could be overridden by subclass templates:
> http://paularmstrong.github.io/swig/docs/#inheritance
> http://jlongster.github.io/nunjucks/templating.html#template-inheritance
>
> An example from Nunjucks:
>
> If we have a template parent.html that looks like this:
>
> {% block header %}This is the default content{% endblock %}
> <section class="left">  {% block left %}{% endblock %}</section>
> <section class="right">  {% block right %}  This is more content  {% endblock %}</section>
>
> And we render this template:
>
> {% extends "parent.html" %}
> {% block left %}This is the left side!{% endblock %}
> {% block right %}This is the right side!{% endblock %}
>
> The output would be:
>
> This is the default content
> <section class="left">  This is the left side!</section>
> <section class="right">  This is the right side!</section>
>
>
> *Alternative Approach to Inhertiance*
>
> Consider random-element which picks a random child node to show whenever a
> user clicks on the element.  This element may show the name of probability
> distribution it uses to pick a child in its shadow DOM.  The name of the
> probability distribution is in the definitions of subclasses of
> random-element, and not in the light DOM of this custom element.  If we
> wanted to use the current inheritance model (multiple generations of shadow
> DOM & shadow element), we have to either replace the entire shadow DOM in
> the subclass to show the name of the probability distribution that
> subclasses use or add an attribute, etc… to identify the element that
> contains the name of probability distribution inside subclasses' shadow
> element. The latter would be weird because there is nothing preventing from
> the user of random-element to put an element that matches the same selector
> as a child of random-element in the "light DOM".
>
> Here, we propose an alternative approach.  We introduce a new "yield"
> element to be used in the superclass define a transclusion point as an
> inheritance hook.  We also introduce "transclude(id, template)" function on
> the element which, upon calling, would create a new shadow DOM on a "yield"
> element of the specified id and populates it with the specified template
> element's content.
>
> Consider the earlier example of my-card and my-webkitten-card.  In this
> new model, the superclass my-card *opts in* to the transclusion by a
> subclass by adding two yield elements in its shadow DOM as follows:
>   Name: <yield id="name"><content select=".name"></content></yield>
>   Email: <yield id="email"><content select=".email"></content></yield>
>
> The complete definition of my-card element, assuming the existence of
> class syntax in ES6 and the use of constructor (as opposed to created
> callback), will look like this:
>   <template id=my-card-template>
>   Name: <yield id="name"><content select=".name"></content></yield>
>   Email: <yield id="email"><content select=".email"></content></yield>
>   </template>
>   <script>
>   class MyCardElement : extends HTMLElement {
>     function constructor() {
>       var shadowRoot = this.createShadowRoot();
>
> shadowRoot.appendChild(document.getElementById('my-card-template').cloneNode(true));
>     }
>   }
>   document.registerElement('my-card', MyCardElement);
>   </script>
>
> If we had an instance of my-card element (in light DOM) as follows:
>   <my-card>
>   <span class="name">R. Niwa</span>
>   <span class="email">rniwa@apple.com</span>
>   </my-card>
>
> Then the composed tree would look like this:
>   <my-card>
>     <!-- shadow root begin -->
>     Name:
>     <yield id="name">
>       <content select=".name">
>         <!-- distribution begin -->
>           <span class="name">R. Niwa</span>
>         <!-- distribution end -->
>       </content>
>     </yield>
>     Email:
>     <yield id="email">
>       <content select=".email">
>         <!-- distribution begin -->
>           <span class="email">rniwa@apple.com</span>
>         <!-- distribution end -->
>       </content>
>     </yield>
>     <!-- shadow root end -->
>   </my-card>
>
> Here, yield elements are behaving like div's because it hasn't been
> transcluded by a subclass.
>
> Now recall that in the current inheritance model, MyWebKittenCardElement,
> a subclass of MyCardElement, could be defined as follows:
>   <template id=my-webkitten-card-template>
>   <shadow>
>     <span class="name">WebKitten</span>
>     <span class="email">kitten@webkit.org</span>
>     <content></content>
>   </shadow>
>   </template>
>   <script>
>   class MyCardElement : extends HTMLElement {
>     function constructor() {
>       this.createShadowRoot().appendChild(document.getElementById('
> my-webkitten-card-templat').cloneNode(true));
>     }
>   }
>   document.registerElement('my-webkitten-card', MyWebKittenCardElement);
>   </script>
>
> In the new model, we write it as follows:
>   <template id=my-webkitten-name-template>
>   <span>WebKitten</span><content select=".name"></content>
>   </template>
>
>   <template id=my-webkitten-email-template>
>   <span>kitten@webkit.org</span><content select=".email"></content>
>   </template>
>
>   <script>
>   class MyCardElement : extends HTMLElement {
>

Should this be class MyWebKittenCardElement : extends MyCardElement?


>     function constructor() {
>       this.transclude('name',
> document.getElementById('my-webkitten-name-template').cloneNode(true));
>       this.transclude('email',
> document.getElementById('my-webkitten-email-template').cloneNode(true));
>     }
>   }
>   document.registerElement('my-webkitten-card', MyWebKittenCardElement);
>   </script>
>
>
> Now suppose we had an instance of my-webkitten-card as follows:
>   <my-webkitten-card>
>   <span class="name">R. Niwa</span>
>   <span class="email">rniwa@apple.com</span>
>   </my-webkitten-card>
>
> Then we have the following composed tree:
>   <my-webkitten-card>
>     <!-- my-card's shadow root begin -->
>     Name:
>     <yield id="name">
>       <!— transclusion begin -->
>       <span>WebKitten</span>
>       <content select=".name">
>         <!-- distribution begin -->
>           <span class="name">R. Niwa</span>
>         <!-- distribution end -->
>       </content>
>       <!— transclusion end -->
>     </yield>
>     Email:
>     <yield id="email">
>       <!— transclusion begin -->
>       <span>kitten@webkit.org</span>
>       <content select=".email">
>         <!-- distribution begin -->
>           <span class="name">rniwa@apple.com</span>
>         <!-- distribution end -->
>       </content>
>       <!— transclusion end -->
>     </yield>
>     <!-- my-webkitten-card's shadow root end -->
>   </my-webkitten-card>
>
> For implementors, this new model doesn't require multiple generations of
> shadow DOM for a single host.  Each element can have at most one shadow
> root, and its shadow DOM simply contain yield element that defines
> transclusion point.  Furthermore, transclusion is simply done as attaching
> a new shadow DOM to yield element.  If we wanted the same expressive power
> as the current inheritance model in grabbing light DOM's nodes, we can make
> insertion points pull nodes out of the parent shadow DOM's light DOM
> instead.
>
> For authors, the new model separates the concerns of binding DOM data
> model to shadow DOM from defining inheritance hooks.  It addresses use
> cases where inheritance hooks for subclasses are separate from data source
> used by custom elements such as random-element showing the name of
> distribution, which is overridden by its subclasses.
>
> - R. Niwa
>
>
Received on Tuesday, 22 April 2014 17:22:55 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 18:14:24 UTC