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 > >