- From: Dimitri Glazkov <dglazkov@chromium.org>
- Date: Tue, 22 Apr 2014 10:22:23 -0700
- To: Ryosuke Niwa <rniwa@apple.com>
- Cc: public-webapps <public-webapps@w3.org>
- Message-ID: <CADh5Ky1-93WOJYA7wEyqiRXtJQXi3f5z+KyyK5b5+-y_CJSxeg@mail.gmail.com>
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