Experimental MDV v2 branch available

147 views
Skip to first unread message

Rafael Weinstein

unread,
Mar 25, 2013, 12:39:44 PM3/25/13
to toolkitchen
The v2 branch of MDV is available here:

https://github.com/toolkitchen/mdv/tree/v2

I'm sure there are bugs lurking, but all (but the view-controller)
tests are working and I think it's worth playing with and reporting
bugs.

There are a number of big changes in this design:

-The .model property on Element & Node is gone.

Data is no longer *directly* bound to the DOM or dynamically
cascade-able (this is the biggest change).

Data is propagated by the act of the <template> element creating instances.

-Text.prototype.addBinding & Element.prototype.addBinding are now gone.

Node.prototype.bind()/unbind()/unbindAll() have replaced them. The
equivalents are:

myText.bind('textContent', obj, path)
myElement.bind(attrName, obj, path)
myInput.bind('value', obj, path)
myInput.bind('checked', obj, path)

Note that the above bind() method does not know how to parse mustache
strings - this a now a concern of <template> instance production (but
is available via HTMLTemplateElement.bindAll -- more below).

It is now up to the node itself to decide what to do with the binding
directive. Custom elements can now override bind/unbind/unbindAll() to
setup two-way bindings.

-<template> "iterate", "instantiate" are now gone. They are replaced
with "repeat", "bind" and "if" (as described in the earlier doc I sent
out).

The syntax is now also that data is only *ever* passed via mustaches.
So those familiar with the old

<template repeat="foo">

should now try

<template repeat="{{ foo }}">

also note that "naked" directives do not currently work, e.g.

<template repeat>.

Instead, you need

<template repeat="{{}}">

-In order to initially bind data to a template, use

HTMLTemplateElement.bindAll(node, model, opt_delegate).

We probably need different API for this (and especially for
registering delegates), but this is what we have for now.

Note that this will traverse the subtree from node and setup all
bindings found in mustaches (this is what <template> uses internally
to setup bindings on instances). If you just want to provide
data/delegate to a template, just specify the template node directly.
If you want to setup mustaches inside a shadowroot, *and* set the
<templates> in motion, use the the shadowroot as the provided node.

-The delegate API is now simpler.

The delegate is still a function, which is called with the contents of
*all* mustaches. e.g.

delegate(mustacheContentString, defaultModel)

The delegate has two valid choices:

1) Return undefined, meaning that it doesn't wish to override the
default behavior.
2) Return an obj, which is expected to have a 'value' property

(2) is entirely general purpose, it allows the delegate to create a
"synthetic" observed value which, itself, can observe whatever it
wishes.

To help with this, template_element.js now exports a global object
called CompoundBinding, which makes it easy to compose a synthetic
value which is composed of other observed values. e.g.

var compound = new CompoundBinding(function(values) {
// values is an object, name => value.
// will be called when one or more dependent values have changed.
return newComputedValue;
});

compound.bind('myThing', model, path);
compound.bind('otherThing', randomObject, path);

I fixed the existing delegates.js to use this new API. Everything
currently works, except the toSource() direction of transforms.

-----

I've got a more complete explainer in the works, but feel free to try
it out and send me bugs/issues/complaints.

R

Rafael Weinstein

unread,
Mar 25, 2013, 12:46:31 PM3/25/13
to toolkitchen
On Mon, Mar 25, 2013 at 9:39 AM, Rafael Weinstein <raf...@google.com> wrote:
> The v2 branch of MDV is available here:
>
> https://github.com/toolkitchen/mdv/tree/v2
>
> I'm sure there are bugs lurking, but all (but the view-controller)
> tests are working and I think it's worth playing with and reporting
> bugs.
>
> There are a number of big changes in this design:
>
> -The .model property on Element & Node is gone.
>
> Data is no longer *directly* bound to the DOM or dynamically
> cascade-able (this is the biggest change).
>
> Data is propagated by the act of the <template> element creating instances.
>
> -Text.prototype.addBinding & Element.prototype.addBinding are now gone.
>
> Node.prototype.bind()/unbind()/unbindAll() have replaced them. The
> equivalents are:
>
> myText.bind('textContent', obj, path)
> myElement.bind(attrName, obj, path)
> myInput.bind('value', obj, path)
> myInput.bind('checked', obj, path)
>
> Note that the above bind() method does not know how to parse mustache
> strings - this a now a concern of <template> instance production (but
> is available via HTMLTemplateElement.bindAll -- more below).
>
> It is now up to the node itself to decide what to do with the binding
> directive. Custom elements can now override bind/unbind/unbindAll() to
> setup two-way bindings.

One thing to note WRT custom elements is the the way mustaches are
interpreted by <template>. Here's how it works:

1) If mustaches takes up the entire string space, e.g. <span>{{ foo
}}</span>, or <span foo="{{ bar }}">, then the default model and
parsed mustache contents are first passed to the delegate, and then
handed to bind(). This is almost certainly what custom elements want

2) Otherwise, a first-level CompoundBinding() is created, which parses
the contents into text runs and replacement and each replacement is
handed to a delegate, e.g.

<span foo="{{ bar }} baz {{ bat }}"> or <span> {{ foo }} </span>.

In these cases, the custom element will *never* be passed the original
string contents of any mustache. The main bummer here is that custom
elements will get it wrong if any whitespace exists outside the
mustache.

Looking for thoughts on how to address this issue.

Scott Miles

unread,
Mar 25, 2013, 12:52:19 PM3/25/13
to Rafael Weinstein, toolkitchen
Thanks for all the detailed info! 

It's going to take us awhile to dive in, so feedback may be delayed a day or two. Will keep you posted.

Scott


--
You received this message because you are subscribed to the Google Groups "Toolkitchen" group.
To unsubscribe from this group and stop receiving emails from it, send an email to toolkitchen...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



Rafael Weinstein

unread,
Mar 25, 2013, 1:09:12 PM3/25/13
to Scott Miles, toolkitchen
No rush.

Also, one thing I forgot to mention:

Node now has a read-only property called 'templateInstance', this will
be an object if the node was produced by a template creating an
instance. It looks like this:

{
firstNode:...
lastNode:...
model:...
}

This serves the purpose of imperative event handlers determining the
"model value" at an arbitrary point in the DOM, but obviously is only
useful for nodes which have been stamped out by templates.

It also serves the purpose of notating the boundary of each template instance.

John Messerly

unread,
Mar 25, 2013, 1:41:48 PM3/25/13
to Rafael Weinstein, Scott Miles, toolkitchen
This is very cool!

Is there any info about the background/motivation for the changes? I'm wondering what will work better/worse compared to the previous design.

Rafael Weinstein

unread,
Mar 25, 2013, 1:51:46 PM3/25/13
to John Messerly, Scott Miles, toolkitchen
There were a couple of motivations:

1) Minimize the platform impact of mdv. This design puts more weight
on the shoulders of the template element (and is possibly more "spec
friendly"
2) Explain the behavior of bindings as being in the purview of the
implementation of nodes. This allows us to explain user-friendly
behavior like treating 'value' specially for input elements, or
allowing custom elements to interpret bindings as two-way. etc...
3) Explain the behavior of the delegate (I think a better name may now
be "custom syntax") consistently: All delegate behavior is always
either do nothing, or return a synthetic binding object. I plan to try
it, but I believe it's now straight forward to implement named scopes
(e.g. <template repeat="user in users">) entirely via the delegate.

Also, there was long-standing discomfort with .model from various
folks. We'll see how we like this new design. It's definitely a
trade-off.

Rafael Weinstein

unread,
Mar 25, 2013, 1:53:09 PM3/25/13
to John Messerly, Scott Miles, toolkitchen
On Mon, Mar 25, 2013 at 10:51 AM, Rafael Weinstein <raf...@google.com> wrote:
> There were a couple of motivations:
>
> 1) Minimize the platform impact of mdv. This design puts more weight
> on the shoulders of the template element (and is possibly more "spec
> friendly"

Note that a platform implementation of MDV might break out the
template directives into their own elements, but because that's a
parser change, it's not a design choice we have at the moment.

Scott Miles

unread,
Mar 27, 2013, 6:10:53 PM3/27/13
to Rafael Weinstein, John Messerly, toolkitchen
'createInstance' does not propagate model bindings as we expect. Please see this Gist.

Is it normal to require the post-instance call to bindTree?

Thanks
Scott

Pete Blois

unread,
Apr 2, 2013, 2:00:35 PM4/2/13
to toolk...@googlegroups.com, Rafael Weinstein, John Messerly
I'm just trying to get up to speed on this, please excuse me if I'm missing some aspects of this-

Is the intention that models can only be applied to template elements and that any time the model is changed (for a detail view in a master-detail scenario), that the document nodes for the details are discarded and re-created?

John Messerly

unread,
Apr 2, 2013, 3:45:10 PM4/2/13
to Pete Blois, toolkitchen, Rafael Weinstein
Just to expand on Pete's point, it seems like this:

HTMLTemplateElement.bindTree(node, model, delegate);
console.log(node.model);

Is equivalent to the old:

node.model = mode;
node.modelDelegate = delegate;
console.log(node.templateInstance.model);

... except it doesn't automatically propagate the model/modelDelegate to child nodes that are added/removed. In other words, it's a one shot apply the model to a tree. Another observation, it seems that calling bindTree again blows away the old tree and creates a new one. The user needs to be careful not to store references to those nodes. Is the expectation is that folks should only call bindTree once?

Overall it seems like it's mostly a subset of the old functionality (except for a few things like templateInstance, where there is now more information). Maybe a tad less powerful from a user's perspective?

Not saying that's bad necessarily :). It seems like it's more efficient to polyfill, which is cool. And the user gets more explicit control over when the template is instantiated. I like how model/modelDelegate are combined into one operation.

(Apologies in advance for any misunderstandings. I'm learning the new design)

John Messerly

unread,
Apr 2, 2013, 4:12:36 PM4/2/13
to Pete Blois, toolkitchen, Rafael Weinstein
Oops, swapped the console.log lines by mistake. I presume y'all get the idea :)

Rafael Weinstein

unread,
Apr 2, 2013, 4:57:48 PM4/2/13
to Pete Blois, toolkitchen, John Messerly
On Tue, Apr 2, 2013 at 11:00 AM, Pete Blois <bl...@google.com> wrote:
> I'm just trying to get up to speed on this, please excuse me if I'm missing
> some aspects of this-
>
> Is the intention that models can only be applied to template elements

This part is strictly true.

> and
> that any time the model is changed (for a detail view in a master-detail
> scenario), that the document nodes for the details are discarded and
> re-created?

This part is more squishy. The behavior of template production is that
it will produce and remove instances based on attempting to follow
some observed object.

For example, if you <template repeat="{{}}"> over an array. When you
remove an element from the array, the template will remove an
instance. When you add an element, it will create an instance.

The master detail use case is somewhat separate. There I would expect
some kind of pattern like:

...
<template bind="{{detail}}">
...

Where you model was something like:

{
data: [],
detail: {}
}

And it's up to your controller to make sure that detail points to the
current item which is focused in the master view.

Rafael Weinstein

unread,
Apr 2, 2013, 5:00:58 PM4/2/13
to John Messerly, Pete Blois, toolkitchen
On Tue, Apr 2, 2013 at 12:45 PM, John Messerly <jmes...@google.com> wrote:
> Just to expand on Pete's point, it seems like this:
>
> HTMLTemplateElement.bindTree(node, model, delegate);
>
> console.log(node.model);
>
>
> Is equivalent to the old:
>
> node.model = mode;
>
> node.modelDelegate = delegate;
> console.log(node.templateInstance.model);
>
> ... except it doesn't automatically propagate the model/modelDelegate to
> child nodes that are added/removed. In other words, it's a one shot apply
> the model to a tree.

Yes.

> Another observation, it seems that calling bindTree
> again blows away the old tree and creates a new one. The user needs to be
> careful not to store references to those nodes. Is the expectation is that
> folks should only call bindTree once?

bindTree() is temporary API. It's mostly for use by the toolkit folks
who want to setup bindings in shadow dom which isn't going to be
stamped out by a template.

I would focus on the semantics of what gets bound to the template
element itself.

>
> Overall it seems like it's mostly a subset of the old functionality (except
> for a few things like templateInstance, where there is now more
> information). Maybe a tad less powerful from a user's perspective?

Yes. The motivation was to simplify the design and hope that no
important use cases are lost. Please speak up if you discover this is
not the case.

Pete Blois

unread,
Apr 2, 2013, 6:58:37 PM4/2/13
to Rafael Weinstein, toolkitchen, John Messerly
So when the detail value is changed it is expected that the template contents are always cleared then re-created?

This seems like it could introduce a number of side-effects including potential undesired focus changes and lack of graceful transitions between values (and potential flashing of more complex components?). One could work around this by basing the bindings from the base model, but would this inhibit reusability of the templates?

For larger apps I find that tracking and debugging the data flow is critical and I frequently want to programmatically set the model at critical independent component boundaries. To do this, the recommendation is that the model value gets wrapped so it can be updated without calling bindTree when the model changes?

Rafael Weinstein

unread,
Apr 2, 2013, 7:15:44 PM4/2/13
to Pete Blois, toolkitchen, John Messerly
No, in the example I gave, the instance is not destroyed and
recreated. The DOM remains stable, and the nodes which have bindings
setup discover that they now point to different values.

Rafael Weinstein

unread,
Apr 2, 2013, 7:54:21 PM4/2/13
to Pete Blois, toolkitchen
Ah. you are correct.

In this example the instance is being destroyed and re-created.
However, this is under your control. If you wanted to preserve the
instance you would instead do something like:

<template bind='{{}}'>
<label>Product Name <input value="{{ selectedItem.name }}"></label>
<label>Price <input value="{{ selectedItem.price }}"></label>
<template ref='description' bind='{{ selectedItem }}'></template>
</template>

On Tue, Apr 2, 2013 at 4:46 PM, Pete Blois <bl...@google.com> wrote:
> I'm just playing with the masterdetail.html sample from use_cases in the mdv
> v2 branch, updated for the latest changes (attached).
>
> What I'm seeing is that when the selection changes that the contents of the
> details template is cleared out then re-created. Could easily be an issue in
> translation on my part though.
>
>
> On Tue, Apr 2, 2013 at 4:15 PM, Rafael Weinstein <raf...@google.com> wrote:
>>
>> No, in the example I gave, the instance is not destroyed and
>> recreated. The DOM remains stable, and the nodes which have bindings
>> setup discover that they now point to different values.
>>

John Messerly

unread,
Apr 2, 2013, 8:03:47 PM4/2/13
to Rafael Weinstein, Pete Blois, toolkitchen
Thanks! That helps clarify.

I wonder if <template bind> should behave the same way as your example code? I thought of <template bind> as just a way to avoid repeating a long path (like a "let" binding in a programming language), but it seems to have the side effect of changing how the template instantiation happens.

Repeat has a somewhat similar issue: if I change an individual list item, it updates smartly, but if I replace the entire list it recreates everything. Sometimes that might be okay because the new list is totally different. But if I was doing something like assigning a filtered list in place of an original list, it might be nice if it didn't recreate all of the DOM.

Rafael Weinstein

unread,
Apr 2, 2013, 8:13:13 PM4/2/13
to John Messerly, Pete Blois, toolkitchen
On Tue, Apr 2, 2013 at 5:03 PM, John Messerly <jmes...@google.com> wrote:
> Thanks! That helps clarify.
>
> I wonder if <template bind> should behave the same way as your example code?
> I thought of <template bind> as just a way to avoid repeating a long path
> (like a "let" binding in a programming language), but it seems to have the
> side effect of changing how the template instantiation happens.

What you are describing is something like JavaScript's *with* scoping
mechanism, correct?

I think this is worth considering: https://github.com/toolkitchen/mdv/issues/30

>
> Repeat has a somewhat similar issue: if I change an individual list item, it
> updates smartly, but if I replace the entire list it recreates everything.

I'm not sure what you mean by "updates smartly". In this case, it
removes the instance and replaces it with a new one.

> Sometimes that might be okay because the new list is totally different. But
> if I was doing something like assigning a filtered list in place of an
> original list, it might be nice if it didn't recreate all of the DOM.

It's possible that repeat could detect that the replaced array retains
some or the same elements as the old array, and keep the old
instances:

https://github.com/toolkitchen/mdv/issues/31

John Messerly

unread,
Apr 2, 2013, 8:34:33 PM4/2/13
to Rafael Weinstein, Pete Blois, toolkitchen
On Tue, Apr 2, 2013 at 5:13 PM, Rafael Weinstein <raf...@google.com> wrote:
On Tue, Apr 2, 2013 at 5:03 PM, John Messerly <jmes...@google.com> wrote:
> Thanks! That helps clarify.
>
> I wonder if <template bind> should behave the same way as your example code?
> I thought of <template bind> as just a way to avoid repeating a long path
> (like a "let" binding in a programming language), but it seems to have the
> side effect of changing how the template instantiation happens.

What you are describing is something like JavaScript's *with* scoping
mechanism, correct?

I think this is worth considering: https://github.com/toolkitchen/mdv/issues/30

Good point. From an observability perspective, it would observe the full path.

(Personally I wouldn't have used the JS "with" analogy :), mainly because "with" is sometimes considered "bad". My observation with language features is that people tend to remember the good/bad association, but not the details of how it became considered that way, often leading to rejecting something even if the underlying problem has been fixed or is not applicable. For example some people have been burned by ASI in JS and therefore assume that newline-as-statement-terminator is a bad language feature, even though it works fine in plenty of other languages.)
 
> Repeat has a somewhat similar issue: if I change an individual list item, it
> updates smartly, but if I replace the entire list it recreates everything.

I'm not sure what you mean by "updates smartly". In this case, it
removes the instance and replaces it with a new one.

Yeah, I worded that confusingly. Meant that only the changed item is replaced.
 
> Sometimes that might be okay because the new list is totally different. But
> if I was doing something like assigning a filtered list in place of an
> original list, it might be nice if it didn't recreate all of the DOM.

It's possible that repeat could detect that the replaced array retains
some or the same elements as the old array, and keep the old
instances:

https://github.com/toolkitchen/mdv/issues/31

Thanks for opening the issues, they capture it well! 

Rafael Weinstein

unread,
Apr 2, 2013, 8:58:54 PM4/2/13
to toolkitchen
FYI. I've merge the v2 branch back to master. v2 is now the "main" mdv.

On Mon, Mar 25, 2013 at 9:39 AM, Rafael Weinstein <raf...@google.com> wrote:
Reply all
Reply to author
Forward
0 new messages