Re: [webcomponents] Imperative API for Insertion Points

> On Feb 16, 2014, at 1:21 AM, Alex Russell <slightlyoff@google.com> wrote:
> 
>> On Sun, Feb 16, 2014 at 12:52 AM, Ryosuke Niwa <rniwa@apple.com> wrote:
>>> On Feb 16, 2014, at 12:42 AM, Ryosuke Niwa <rniwa@apple.com> wrote:
>>> 
>>>> On Feb 15, 2014, at 11:30 PM, Alex Russell <slightlyoff@google.com> wrote:
>>>> 
>>>>> On Sat, Feb 15, 2014 at 4:57 PM, Ryosuke Niwa <rniwa@apple.com> wrote:
>>>>> Hi all,
>>>>> 
>>>>> Ifd like to propose one solution for
>>>>> 
>>>>> [Shadow]: Specify imperative API for node distribution
>>>>> https://www.w3.org/Bugs/Public/show_bug.cgi?id=18429
>>>>> 
>>>>> because select content attribute doesnft satisfy the needs of framework/library authors to support conditionals in their templates,
>>>>> and doesnft satisfy my random image element use case below.
>>>>> 
>>>>> 
>>>>> == Use Case ==
>>>>> Random image element is a custom element that shows one of child img elements chosen uniformally random.
>>>>> 
>>>>> e.g. the markup of a document that uses random-image-element may look like this:
>>>>> <random-image-element>
>>>>>   <img src="kitten.jpg">
>>>>>   <img src="cat.jpg">
>>>>>   <img src="webkitten.jpg">
>>>>> </random-image-element>
>>>>> 
>>>>> random-image-element displays one out of the three img child elements when a user clicks on it.
>>>>> 
>>>>> As an author of this element, I could modify the DOM and add style content attribute directly on those elements
>>>>> but I would rather use shadow DOM to encapsulate the implementation.
>>>>> 
>>>>> 
>>>>> == API Proposal ==
>>>>> 
>>>>> Add two methods void add(Element) and void remove(Element) to content element.
>>>>> (We can give them more descriptive names. I matched select element for now).
>>>>> 
>>>>> Each content element has an ordered list of *explicitly inserted nodes*.
>>>>> 
>>>>> add(Element element) must act according to the following algorithm:
>>>>> If the content element's shadow host's node tree doesn't contain _element_, throw HierarchyRequestError.
>>>>> If element is already in some other content element's _explicitly inserted nodes_
>>>>> then call remove with _element_ on that content element.
>>>>> Append _element_ to the end of _explicitly inserted nodes_.
>>>>> 
>>>>> remove(Element element) must act according to the following algorithm:
>>>>> If the content element's _explicitly inserted nodes_ does not contain _element_, throw NotFoundError.
>>>> 
>>>> Throwing exceptions is hostile to usability.
>>> 
>>> If people are so inclined, we donft have to throw an exception and silently fail.
>>>>> Remove _element_ from _explicitly inserted nodes_.
>>>>> 
>>>>> The idea here is that _explicitly inserted nodes_ of an insertion point A would be the list of distributed nodes of A but
>>>>> I haven't figured out exactly how _explicitly inserted nodes_ should interact with select content attribute.
>>>>> 
>>>>> I think the simplest model would be _explicitly inserted nodes_ simply overriding whatever select content attribute was
>>>>> trying to do but I don't have a strong opinion about how they should interact yet.
>>>>> 
>>>>> I don't think it makes sense to support redistributions, etc... at least in the initial API.
>>>>> 
>>>>> 
>>>>> This proposal has an advantage over the existing proposal on https://www.w3.org/Bugs/Public/show_bug.cgi?id=18429:
>>>>> It doesn't require UA calling back to JS constantly to match elements
>>>>> Point 1 implies we don't expose when distribution happens for select content attribute.
>>>> This doesn't seem like progress. I'd hope an imperative API would, instead, be used to explain how the existing system works and then propose layering that both accommodates the existing system and opens new areas for programmatic use.
>>>> 
>>>> We can imagine such a system for programmatic Shadow DOM with some sort of distribute(Element) callback that can be over-ridden and use add/remove methods to do final distribution.
>>> 
>>> The problem here is that such a callback must be called on every node upon any state change because UAs have no way of knowing what causes redistribution for a given component.  As as a matter of fact, some use cases may involve changing the node distributions based on some JS objects state.  And having authors codify such conditions for UAs is much more cumbersome than letting them re-distribute nodes at their will.
>> 
>> To give you more concrete example, in the case of my random image element, how can UA notice that user clicking on the element should trigger reconstruction of the composed tree?
> 
> Isn't the stated design of the custom element that it re-constructs the composed tree with a random image every time it's clicked? It's not actually clear what you wanted here because there isn't any example code to go on.
>  
>>  Should the script call some method like redistribute() on the host upon click?  But then, since the element needs to pick a child uniformly random, it probably needs to keep track of the number of children to be distributed and return true exactly when that node was passed into the callback.  Thatfs an extremely cumbersome API at least for my use case.
> 
> I have the sense that if you produced example code you'd be able to make a better guess about what's onerous and what isn't. As it is, we're debating hypotheticals.
> 
> Here's a version of your component based on my proposal. I don't feel it's particularly cumbersome in context:
> 
>     var log = console.log.bind(console);
>     var randomInt = function(min, max) {
>       return Math.floor(Math.random() * (max - min + 1)) + min;
>     }
> 
>     var rip = Object.create(HTMLElement.prototype);
>     rip.distributeCallback = function(children) {
>       while(this.shadowRoot.children.length) {
>         this.shadowRoot.remove(this.shadowRoot.children[0]);
>       }
>       var i = randomInt(0, children.length-1);
>       this.shadowRoot.add(children[i]);
>     };
>     var RandomImage = document.registerElement(
>                           "random-image", { prototype: rip });
> 
> 
> Obviously, the element needs to add a click handler to call distributeCallback() manually with this.children, but I don't see how that's hard.

And what exactly is the point of having this callback?  Are UAs supposed to call it automatically in some situations?  If so, when?

- R. Niwa

Received on Monday, 17 February 2014 02:05:14 UTC