Permalink
Browse files

Remove HTMLTemplateElement.bindTree and implement template.model

  • Loading branch information...
rafaelw committed Apr 18, 2013
1 parent c22a7e6 commit b31c6fc016ea81088059f0b628dc253ade201cf8
View
@@ -34,7 +34,7 @@ There's plenty of detail, but it all hinges on the `<template>` element. Let’s
{ what: 'GoodBye', who: 'Imperative' }
]
};
- HTMLTemplateElement.bindTree(t, model);
+ t.model = model;
</script>
</body>
@@ -43,15 +43,15 @@ This example should look mostly familiar to anyone who knows HTML, but there are
#### The `<template>` element
The [HTML Template element](http://www.w3.org/TR/html-templates/) is new and browsers are in the process of implementing it. It [allows](http://www.html5rocks.com/en/tutorials/webcomponents/template/) you to declare fragments of HTML that may be used at some point. The [Chrome Inspector](https://developers.google.com/chrome-developer-tools/docs/overview) allows you see the [content](http://www.w3.org/TR/html-templates/#api-html-template-element-content) of a template element.
-
+
![ScreenShot](https://raw.github.com/toolkitchen/mdv/master/docs/images/README/templateContent.png)
If you loaded the above example without `<script src="src/mdv.js"></script>`, that’s about all `<template>` would do.
However, the MDV library teaches `<template>` some new tricks. With MDV, `<template>` knows how to:
* Instruct DOM nodes to derive their value from JavaScript data by binding them to the data provided.
-* Maintain a fragment of DOM (or "instance fragment") for each item in an array.
+* Maintain a fragment of DOM (or "instance fragment") for each item in an array.
* Conditionally stamp out one or more instance fragments, based on whether some data value is true or not.
* ...And lots more.
@@ -76,7 +76,7 @@ In `<script>`, we create a model:
Notice that this is just JavaScript data: _there’s no need to import your data into special observable objects_. The template is set in motion by binding the model data to it:
- HTMLTemplateElement.bindTree(t, model);
+ t.model = model;
Now the template is off to the races. Here's the result:
@@ -113,50 +113,50 @@ Getting the idea? MDV allows you author your HTML _using_ HTML which contains in
</p>
If you are new to MDV, the best to place to go is to the look at the [How-To examples](https://github.com/toolkitchen/mdv/tree/master/examples/how_to). These are little examples which succinctly demonstrate how to use MDV to accomplish things that frequently are required for real web apps:
-
+
_Binding to DOM values:_
* [Binding to text values](https://github.com/toolkitchen/mdv/blob/master/examples/how_to/bind_to_text.html): How to insert values into the DOM that render as text.
* [Binding to attributes](https://github.com/toolkitchen/mdv/blob/master/examples/how_to/bind_to_attributes.html): How to insert values into element attributes
* [Conditional attributes](https://github.com/toolkitchen/mdv/blob/master/examples/how_to/conditional_attributes.html): How to bind to attributes such that the attribute is only present if the binding value is “truthy”.
* [Binding to input elements](https://github.com/toolkitchen/mdv/blob/master/examples/how_to/bind_to_input_elements.html): How to bind bi-directionally with input elements.
* Custom bindings: How to implement a custom element which has a specialized interpretation of a binding.
-
+
_Using `<template>` to produce DOM structures:_
* Conditionals: How to control whether instance fragments are produced based on the value of a binding.
* Nested templates: How to accomplish nested template production.
* Re-using templates: How to define a template once and use it in more than one location.
* Recursive templates: How to produce tree-structure DOM whose depth is dependent on the data to which it is bound.
-
+
### API Reference / Pseudo-specs
MDV is designed to as two primitives which could eventually become standardized and implemented natively in browsers. The following two documents specify their behavior, API and use.
* `Node.bind`: Which describes how DOM nodes are bound to data values
* `<template>` instantiation: Which describes how `<template>` manages instance fragments.
-
+
### Extending MDV
MDV is mainly concerned with being robust and efficient in interacting with application data and keeping the DOM in sync , but more advanced behaviors can be accomplished via one or both of the following:
* [A Custom Syntax API](https://github.com/toolkitchen/mdv/blob/master/docs/syntax.md)
* Chained observation
-
+
### Advanced Topics
* DOM Stability: MDV makes every effort to maintain the state of DOM nodes (event listeners, expandos, etc...). Understand why this is important and how it works.
* Imperative DOM mutation: You should rarely need to directly manipulate the DOM, but if you do, it’s allowed. Learn the simple rules of how MDV will react if you manipulate the DOM it is managing.
* Asynchronous processing model: MDV responds asynchronously to changes in data and DOM. Learn why this is good and what it means for your application.
-
+
### Deployment
MDV builds upon recently added primitives to the Web Platform:
* [ECMAScript Object.observe](http://updates.html5rocks.com/2012/11/Respond-to-change-with-Object-observe)
* [The HTML Template Element](http://www.html5rocks.com/en/tutorials/webcomponents/template/)
* [DOM Mutation Observers](https://developer.mozilla.org/en-US/docs/DOM/MutationObserver)
-
+
Not all browsers currently implement all the required primitives. MDV attempts to polyfil their absence, but targeting browsers which do not support all three requires understanding patterns of use which should be prefered or avoided to ensure proper behavior.
* Deploying MDV, supported browsers and rough edges
@@ -12,18 +12,21 @@ <h1>Bind To Attributes</h1>
<script>
var t = document.getElementById('colors');
-var model = {
+t.model = {
colors: [
{ color: 'red' },
{ color: 'blue' },
{ color: 'green' },
{ color: 'pink' }
]
};
-HTMLTemplateElement.bindTree(t, model);
+
+var hasObserve = typeof Object.observe === 'function';
var b = document.getElementById('rotateText');
b.addEventListener('click', function() {
- model.colors.push(model.colors.shift());
+ t.model.colors.push(t.model.colors.shift());
+ if (!hasObserve)
+ Model.notifyChanges();
});
</script>
@@ -30,7 +30,7 @@ <h2>Radio</h2>
<script>
var t = document.getElementById('input');
-var model = {
+t.model = {
input: {
amount: 10,
toggle: true,
@@ -39,5 +39,4 @@ <h2>Radio</h2>
radio3: false
}
};
-HTMLTemplateElement.bindTree(t, model);
</script>
@@ -12,18 +12,21 @@ <h1>Bind To Text</h1>
<script>
var t = document.getElementById('text');
-var model = {
+t.model = {
text: [
{ value: 'Fee' },
{ value: 'Fi' },
{ value: 'Fo' },
{ value: 'Fum' }
]
};
-HTMLTemplateElement.bindTree(t, model);
+
+var hasObserve = typeof Object.observe === 'function';
var b = document.getElementById('rotateText');
b.addEventListener('click', function() {
- model.text.push(model.text.shift());
+ t.model.text.push(t.model.text.shift());
+ if (!hasObserve)
+ Model.notifyChanges();
});
</script>
@@ -9,10 +9,9 @@ <h1>Conditional Attributes</h1>
<script>
var t = document.getElementById('addRemoveAttribute');
-var model = {
+t.model = {
data: {
hide: false
}
};
-HTMLTemplateElement.bindTree(t, model);
</script>
@@ -34,9 +34,9 @@
var controller = new this[controllerClass](node);
if (controller.model) {
- // TODO(rafaelw): This should really only visit template elements
- // TODO(rafaelw): It's pretty lame you have to set the delegate here.
- HTMLTemplateElement.bindTree(node, controller.model);
+ HTMLTemplateElement.forAllTemplatesFrom_(node, function(t) {
+ t.model = controller.model;
+ });
}
node.controller = controller;
}
View
@@ -18,6 +18,6 @@ <h1>Model-driven Views</h1>
{ what: 'GoodBye', who: 'Imperative' }
]
};
-HTMLTemplateElement.bindTree(t, model);
+t.model = model;
</script>
<body>
View
@@ -377,10 +377,6 @@
return tagName.toLowerCase() + '[template]';
}).join(', ');
- function getTemplateDescendentsOf(node) {
- return node.querySelectorAll(allTemplatesSelectors);
- }
-
function isAttributeTemplate(el) {
return semanticTemplateElements[el.tagName] &&
el.hasAttribute('template');
@@ -437,19 +433,21 @@
Model.notifyChanges();
}, false);
+ function forAllTemplatesFrom(node, fn) {
+ var subTemplates = node.querySelectorAll(allTemplatesSelectors);
+
+ if (isTemplate(node))
+ fn(node)
+ forEach(subTemplates, fn);
+ }
+
function bootstrapTemplatesRecursivelyFrom(node) {
function bootstrap(template) {
if (!HTMLTemplateElement.decorate(template))
bootstrapTemplatesRecursivelyFrom(template.content);
}
- // Need to do this first as the contents may get lifted if |node| is
- // template.
- var templateDescendents = getTemplateDescendentsOf(node);
- if (isTemplate(node))
- bootstrap(node);
-
- forEach(templateDescendents, bootstrap);
+ forAllTemplatesFrom(node, bootstrap);
}
if (!hasTemplateElement) {
@@ -577,8 +575,6 @@
// Review whether this is the right public API.
HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
- HTMLTemplateElement.bindTree = addBindings;
-
var htmlElement = global.HTMLUnknownElement || HTMLElement;
var contentDescriptor = {
@@ -621,6 +617,8 @@
return ref ? ref.content : template.content;
}
+ var templateModelTable = new SideTable('templateModel');
+
mixin(HTMLTemplateElement.prototype, {
bind: function(name, model, path) {
switch (name) {
@@ -679,6 +677,15 @@
return instance;
},
+ get model() {
+ return templateModelTable.get(this);
+ },
+
+ set model(model) {
+ templateModelTable.set(this, model);
+ addBindings(this, model);
+ },
+
get ref() {
var ref;
var refId = this.getAttribute('ref');
@@ -1211,7 +1218,7 @@
enumerable: true
})
- // Expose for testing
- HTMLTemplateElement.allTemplatesSelectors = allTemplatesSelectors;
-
+ // Polyfill-specific API.
+ HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
+ HTMLTemplateElement.bindAllMustachesFrom_ = addBindings;
})(this);
@@ -38,7 +38,7 @@ suite('Element Bindings', function() {
var text = document.createTextNode('hi');
var model = {a: 1, b: 2};
text.textContent = '{{a}} and {{b}}';
- HTMLTemplateElement.bindTree(text, model);
+ HTMLTemplateElement.bindAllMustachesFrom_(text, model);
Model.notifyChanges();
assert.strictEqual('1 and 2', text.data);
@@ -110,7 +110,7 @@ suite('Element Bindings', function() {
var el = document.createElement('div');
el.textContent = 'dummy';
el.firstChild.textContent = 'Hello {{ adj }} {{noun}}!';
- HTMLTemplateElement.bindTree(el, model);
+ HTMLTemplateElement.bindAllMustachesFrom_(el, model);
Model.notifyChanges();
assert.strictEqual('Hello cruel world!', el.textContent);
@@ -328,7 +328,7 @@ suite('Element Bindings', function() {
var el = document.createElement('div');
var model = {foo: 'bar'};
el.setAttribute('foo', '{{foo}} {{foo}}');
- HTMLTemplateElement.bindTree(el, model);
+ HTMLTemplateElement.bindAllMustachesFrom_(el, model);
Model.notifyChanges();
assert.strictEqual('bar bar', el.getAttribute('foo'));
});
View
@@ -27,16 +27,19 @@ suite('Syntax', function() {
div.innerHTML = s;
testDiv.appendChild(div);
- Array.prototype.forEach.call(div.querySelectorAll(
- HTMLTemplateElement.allTemplatesSelectors),
- function(t) {
- HTMLTemplateElement.decorate(t);
- }
- );
+ HTMLTemplateElement.forAllTemplatesFrom_(div, function(template) {
+ HTMLTemplateElement.decorate(template);
+ });
return div;
}
+ function recursivelySetTemplateModel(node, model) {
+ HTMLTemplateElement.forAllTemplatesFrom_(node, function(template) {
+ template.model = model;
+ });
+ }
+
test('Registration', function() {
var model = { foo: 'bar'};
var testData = [
@@ -78,7 +81,7 @@ suite('Syntax', function() {
var div = createTestHtml(
'<template bind syntax="Test">{{ foo }}' +
'<template bind>{{ foo }}</template></template>');
- HTMLTemplateElement.bindTree(div, model);
+ HTMLTemplateElement.bindAllMustachesFrom_(div, model);
Model.notifyChanges();
assert.strictEqual(4, div.childNodes.length);
assert.strictEqual('bar', div.lastChild.textContent);
@@ -112,7 +115,7 @@ suite('Syntax', function() {
var div = createTestHtml(
'<template bind syntax="2x">' +
'{{ foo }} + {{ 2x: bar }} + {{ 4x: bar }}</template>');
- HTMLTemplateElement.bindTree(div, model);
+ recursivelySetTemplateModel(div, model);
Model.notifyChanges();
assert.strictEqual(2, div.childNodes.length);
assert.strictEqual('2 + 8 + ', div.lastChild.textContent);
@@ -182,7 +185,7 @@ suite('Syntax', function() {
var div = createTestHtml(
'<template bind syntax="Test">{{ foo }}' +
'<template bind syntax="Test2">{{ foo }}</template></template>');
- HTMLTemplateElement.bindTree(div, model);
+ recursivelySetTemplateModel(div, model);
Model.notifyChanges();
assert.strictEqual(4, div.childNodes.length);
assert.strictEqual('bar', div.lastChild.textContent);
Oops, something went wrong.

0 comments on commit b31c6fc

Please sign in to comment.