On Apr 22, 2014, at 10:22 AM, Dimitri Glazkov <dglaz...@chromium.org> wrote:

> 
> 
> 
> On Thu, Apr 17, 2014 at 2:42 AM, Ryosuke Niwa <rn...@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">rn...@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">rn...@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">kit...@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">rn...@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">rn...@webkit.org</span>
>         <span class="email">kit...@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 kit...@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">kit...@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">rn...@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">rn...@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">kit...@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>kit...@webkit.org</span><content select=".email"></content>
>   </template>
> 
>   <script>
>   class MyCardElement : extends HTMLElement {
> 
> Should this be class MyWebKittenCardElement : extends MyCardElement?

Oops, yes.

>     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">rn...@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>kit...@webkit.org</span>
>       <content select=".email">
>         <!-- distribution begin -->
>           <span class="name">rn...@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
> 
> 

Reply via email to