Advanced topics
Table of contents
This section covers advanced topics that you don’t need to understand to get data binding working in a Polymer application.
How data binding works
It may be easiest to understand what data binding is, by first understanding what data binding is not – it doesn’t work like traditional template systems.
In a traditional AJAX application, templating works by replacing innerHTML for some container element. Where the container contains a non-trivial DOM subtree, this has two drawbacks:
Replacing the existing child nodes destroys the transient state of the DOM nodes, such as event listeners and form input values. The entire set of nodes is destroyed and recreated, even if only a few values change.
In contrast, Polymer data binding makes the smallest changes to the DOM necessary. The DOM nodes representing a template instance are maintained as long as the corresponding model data is in place.
Consider the following DOM, which represents a template and the template instances it manages:
<table>
<template repeat="{{item in items}}">
<tr><td>{{item.name}}</td><td>{{item.count}}</td></tr>
</template>
<tr><td> Bass </td><td> 7 </td></tr>
<tr><td> Catfish </td><td> 8 </td></tr>
<tr><td> Trout </td><td> 0 </td></tr>
</table>
If you re-sort the array by item.count
, Polymer simply swaps the order of the corresponding DOM subtrees. No nodes are created or destroyed, and the only mutation is the re-ordering of two nodes (in bold):
<table>
<template repeat="{{item in items}}">
<tr><td> {{item.name}} </td><td> {{item.count}} </td></tr>
</template>
<tr><td> Catfish </td><td> 8 </td></tr>
<tr><td> Bass </td><td> 7 </td></tr>
<tr><td> Trout </td><td> 0 </td></tr>
</table>
If you change item.count
for one of the objects, the only thing that changes in the DOM tree is the binding value (in bold):
<table>
<template repeat="{{item in items}}">
<tr><td> {{item.name}} </td><td> {{item.count}} </td></tr>
</template>
<tr><td> Catfish </td><td> 8 </td></tr>
<tr><td> Bass </td><td> 7 </td></tr>
<tr><td> Trout </td><td> 2 </td></tr>
</table>
How data binding tracks template instances
When a template generates one or more instances, it inserts the instances immediately after itself. So the template itself serves as a marker for where the first instance starts. For each template instance, the template keeps track of the terminating node in the template instance. For the simple case, the terminating node is a clone of the last node in the template itself.
The following diagram represents the DOM for a template and its instances:
<template repeat="{{item in myList}}"> <img> <span>{{item.name}}</span> </template> <img> <span>foo</span> ⇐ terminating node in template instance <img> <span>bar</span> ⇐ terminating node in template instance <img> <span>baz</span> ⇐ terminating node in template instance
All sibling nodes (and their children) following the template node up to and including the first terminating node make up the DOM for the first template instance. Each subsequent template instance is identified the same way.
If the objects in the myList array are moved or deleted, the template can move or remove the corresponding DOM nodes.
In the case of nested templates, identifying the terminating node is somewhat more complicated. Consider the following templates:
<template repeat="{{user in users}}">
<p>{{user.name}}</p>
<template repeat="{{alias in user.aliases}}">
<p>a.k.a. {{alias}}</p>
</template>
</template>
In this case, the last node in the outer template is the inner template. However, when the template instances are created, the inner template generates its own instances. (In the following example, whitespace is added around the template instances for readability.)
<template repeat="{{user in users}}"> <p>{{user.name}}</p> <template repeat="{{alias in user.aliases}}"> <p>a.k.a. {{alias}}</p> </template> </template> <p>Bob</p> ⇐ start of 1st outer template instance <template repeat="{{alias in user.aliases}}"> <p>a.k.a. {{alias}}</p> </template> <p>a.k.a. Lefty</p> ⇐ 1st inner template instance <p>a.k.a. Mr. Clean</p> ⇐ 2nd inner template instance (terminating node for outer template instance) <p>Elaine</p> ⇐ start of 2nd outer template instance <template repeat="{{alias in user.aliases}}"> <p>a.k.a. {{alias}}</p> </template> <p>a.k.a. The Wiz</p> ⇐ 1st inner template instance (terminating node for outer template instance)
In this case, note that the terminating node of the outer instance is the same as the terminating node of the last inner instance.
Mutating template-generated DOM nodes
In general, you shouldn’t need to manually mutate the DOM nodes generated by template bindings—you can do most things you need to do simply by setting up bindings and mutating the model object.
If you do need to mutate the DOM nodes generated by a template, it is safe to do so as long as you don’t remove the terminating node of a template instance. The easiest way to do this is to wrap the template contents in a container element:
<template repeat="{{item in listItems}}">
<section on-click={{rowSelected}}>
<h2>{{item.title}}</h2>
<p>{{item.text}}</p>
</section>
</template>
In this case, the outer <section> will serve as the terminating node for each template instance. You can mutate the DOM nodes inside each <section> as long as you don’t remove the <section> node itself. For example, the rowSelected event handler invoked when a section is clicked could do something like this:
rowSelected: function(e, detail, sender) {
var blinkTag = document.createElement('blink-tag');
blinkTag.textContent = 'BREAKING NEWS';
sender.insertBefore(blinkTag, sender.querySelector('p'));
}
Note: In practice, it would be easier and cleaner to set a value on the model and use a conditional template. This example just demonstrates how the data binding system handles mutation.
Clicking on a row results in a DOM change like this (whitespace added for readability):
<template repeat="{{item in listItems}}">
<section on-click={{rowSelected}}>
<h2>{{item.title}}</h2>
<p>{{item.text}}</p>
</section>
</template>
<section>
<h2>Bigfoot spotted in lower Haight!</h2>
<p>Police cite, release famous urban legend.</p>
</section>
<section>
<h2>Shadow DOM lands in all major browsers</h2>
<blink-tag>BREAKING NEWS</blink-tag>
<p>Cheering crowds greet announcement.</p>
</section>
Because the template identifies each instance by the terminating node, changes to the instance’s state persist even if the template has to reorder its instances:
<template repeat="{{item in listItems}}">
<section on-click={{rowSelected}}>
<h2>{{item.title}}</h2>
<p>{{item.text}}</p>
</section>
</template>
<section>
<h2>Shadow DOM lands in all major browsers</h2>
<blink-tag>BREAKING NEWS</blink-tag>
<p>Cheering crowds greet announcement.</p>
</section>
<section>
<h2>Bigfoot spotted in lower Haight!</h2>
<p>Police cite, release famous urban legend.</p>
</section>
Of course, if you change one of the values that’s bound, it will be overwritten the next time the underlying model data changes. The two-way data binding only registers DOM changes to input elements – not imperative changes to arbitrary DOM nodes.
Using data binding outside of a Polymer element
This Polymer data binding works inside a Polymer element. If you want to use data binding elsewhere, there are two options:
-
If you’re using Polymer, use an auto-binding template to take advantage of data binding without creating a new custom element.
-
If you aren’t using the rest of Polymer, use the Template Binding library directly. The template binding library is used internally by Polymer, and can be used directly, with or without the rest of Polymer. (Note that if you use template binding by itself, you cannot use Polymer expressions.)
Note: Earlier versions of Polymer included an element called <polymer-body>
.
If you were using <polymer-body>
previously, the closest substitute is an auto-binding template.
Using the auto-binding template element
The auto-binding
element is a Polymer custom element that extends the standard
<template>
element. You can use it when you want to use Polymer data
binding in a page without having to create a custom element just for this purpose. Auto-binding
templates support a subset of the features available when you create your own custom element:
- Full-featured data binding, with Polymer expressions.
- Declarative event mapping.
- Automatic node finding.
For an auto-binding template, the data model is on the template itself. For example, to use data binding at the top level of a page:
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="../../components/polymer/polymer.html">
</head>
<body>
<!-- render data set -->
<template id="auto-bind-demo" is="auto-binding" repeat="{{quotes}}">
<div on-tap="{{quoteClicked}}">
<h3>{{quote}}</h3>
- <em>{{attribution}}</em>
</div>
</template>
<script>
var t = document.querySelector('#auto-bind-demo');
t.quoteClicked = function() {
alert('Quote clicked!');
};
t.quotes = [{
attribution: 'Plautus',
quote: 'Let deeds match words.'
}, {
attribution: 'Groucho Marx',
quote: 'Time flies like an arrow. Fruit flies like a banana.'
}];
</script>
</body>
</html>
{{quote}}
- {{attribution}}The auto-binding template inserts the instances it creates immediately after
itself in the DOM tree (not in its shadow DOM). In this case, the quotes are
inserted as children of the body
element.
After adding the instances, the auto-binding template fires the template-bound
event:
template.addEventListener('template-bound', function() {
// template has been stamped.
});
The auto-binding
template is currently included automatically when you load the
Polymer library.
Inserting data-bound HTML
The Polymer data binding escapes any HTML in the bound data. This avoids many potential security pitfalls with including arbitrary HTML.
However, for those special cases where you need to add HTML dynamically, Polymer
elements provide the injectBoundHTML
instance method. injectBoundHTML
replaces
the contents of a target element with an arbitrary block of HTML. Any data binding
expressions in the HTML are bound to the element.
For example, in the following example, a message is injected into the message_area
<div>
.
Changing the message
property changes the data displayed in the message_area
.
<polymer-element name="my-element">
<template>
<div id="message_area"></div>
</template>
<script>
Polymer({
message: 'hi there',
ready: function() {
this.injectBoundHTML('<b>{{message}}</b>', this.$.message_area);
}
});
</script>
</polymer-element>
Note that the rules for data binding using injectBoundHTML
are the same as the rules for
standard data binding. For example, if message
contains HTML, the HTML is escaped.