HTMLBars serves as a polyfill for a native implement of node.bind
.
Instead of letting the browser parse the HTML and generate nodes, which leaves us at the mercy of the limitations of the parser, HTMLBars implements an HTML parser and generates the nodes itself.
As HTMLBars generates the nodes, it calls into node.bind
if it detects mustache syntax.
Open questions:
- MDV "syntaxes"
In order to avoid needing to monkey-patch HTMLElement in the polyfill, we will instantiate wrapper objects that manage the binding.
So instead of:
textNode.bind('textContent', model, 'firstName');
We will do this:
var textNodeWrapper = new WRAPPERS.Text(textNode);
fragWrapper.appendChildWrapper(textNodeWrapper);
textNodeWrapper.bind('textContent', model, 'firstName');
The wrappers keep references to their children, which enables recursive disposal when the root of a wrapper tree is disposed of.
This template:
<p>Enter {{foo}} Exit</p>
Will execute something like this code:
// <p>
var p = document.createElement('p');
// Text Node
p.appendChild(document.createTextNode('Enter ');
// Bound Text Node
var foo = document.createTextNode('');
var fooWrapper = new WRAPPERS.Text(foo);
fragWrapper.appendChildWrapper(fooWrapper);
// {{foo}}
fooWrapper.bind('textContent', model, 'foo');
p.appendChild(foo);
// Text Node
p.appendChild(document.createTextNode(' Exit');
// </p>
frag.appendChild(p);
In this example, fragWrapper
is an object that was associated with the template instantiation. When the instance of the template is destroyed, child wrappers will be recursively destroyed as well.
NOTE: MDV uses "dispose" terminology where Ember typically uses "destroy"
This template:
<a href="{{url}}">{{foo}}</a>
<p title="static">Contents</p>
Will execute something like this code:
// <a>
var a = document.createElement('a');
var aWrapper = new WRAPPERS.HTMLAnchorElement(a);
fragWrapper.appendChildWrapper(aWrapper);
// href="{{url}}"
aWrapper.bind('href', model, 'url');
// Bound Text Node
var foo = document.createTextNode('');
var fooWrapper = new WRAPPERS.Text(foo);
aWrapper.appendChildWrapper(fooWrapper);
// {{foo}}
fooWrapper.bind('textContent', model, 'foo');
a.appendChild(foo);
// </a>
frag.appendChild(a);
// <p>
var p = document.createElement('p');
// title="static"
p.setAttribute('title', 'static');
// Text Node
p.appendChild(document.createTextNode('Contents'));
// </p>
frag.appendChild(p);
Paths would work by generating a PathBinding
instead of binding directly to the model:
<a href="{{link.url}}">Click here</a>
Would generate code like this:
// <a>
var a = document.createElement('a');
var aWrapper = new WRAPPERS.HTMLAnchorElement(a);
fragWrapper.appendChildWrapper(aWrapper);
// href="{{link.url}}"
var linkUrlBinding = new PathBinding(model, 'link.url');
aWrapper.bind('href', linkUrlBinding, 'value');
// Text Node
a.appendChild(document.createTextNode('Click here');
// </a>
frag.appendChild(a);
The PathBinding
is an object that updates its value
property whenever any link in the chain changes. A normal Object.observe
on its value
property will "just work".
It's possible to have an attribute that contains both static and dynamic parts. These attributes are represented by CompoundBinding
s.
<a href="{{scheme}}://{{host}}:{{port}}/{{path}}">Click here</a>
This would generate code like this:
// <a>
var a = document.createElement('a');
var aWrapper = new WRAPPERS.HTMLAnchorElement(a);
fragWrapper.appendChildWrapper(aWrapper);
// href="{{scheme}}://{{host}}:{{port}}/{{path}}"
var compoundBinding = new CompoundBinding([
new Binding(model, 'scheme'),
{ value: "://" },
new Binding(model, 'host'),
{ value: ":" },
new Binding(model, 'port'),
{ value: "/" },
new Binding(model, 'path')
]);
aWrapper.bind('href', compoundBinding, 'value');
// Text Node
a.appendChild(document.createTextNode('Click here');
// </a>
frag.appendChild(a);
A CompoundBinding
observes the value
property of all elements in the Array and updates its value
property whenever any of them change.
A Binding
observes a property on an object and updates its value
property whenever that property changes.
Note that a CompoundBinding
may contain PathBinding
s, since PathBinding
s also expose an observable value
property as its surface area.
It would be possible to special-case Binding
and static strings to improve efficiency, but I've opted not to for now to keep the implementation of CompoundBinding
simple.
Ember would register Ember.View
s as the element wrappers.
The same strategy could work with node.bind
:
- register the elements themselves as the wrappers
- implement a no-op
appendChildWrapper
if disposal is not required
Awesome, where can we watch/try the progress? the main repo is outdated I think