Data binding overview
Table of contents
Polymer supports two-way data binding. Data binding extends HTML and the DOM APIs to support a sensible separation between the UI (DOM) of an application and its underlying data (model). Updates to the model are reflected in the DOM and user input into the DOM is immediately assigned to the model.
For Polymer elements, the model is always the element itself. For example, consider a simple element:
<polymer-element name="name-tag">
<template>
This is <b>{{owner}}</b>'s name-tag element.
</template>
<script>
Polymer('name-tag', {
// initialize the element's model
ready: function() {
this.owner = 'Rafael';
}
});
</script>
</polymer-element>
Here the owner
property is the model for the name-tag
element. If you update the owner
property:
document.querySelector('name-tag').owner = 'June';
You change the contents of the tag:
This is June’s name-tag element.
The <template>
element
The HTML Template element allows you to declare chunks of inert HTML that can be cloned and used at some later point. The contents of the <template>
element are hidden in the sense that they aren’t rendered in the browser and can’t be retrieved by querySelector
; and inactive in the sense that they don’t cause resources to be loaded or scripts to be run.
In Polymer, templates have two special purposes:
-
In a Polymer element declaration, the first (top-level)
<template>
element is used to define the custom element’s shadow DOM. -
Inside a Polymer element, you can use templates with data binding to render dynamic content.
Note: The <template>
element is a new element in the HTML standard. For information on using templates
outside of Polymer, see HTML’s New Template Tag
on HTML5Rocks.
Templates with data binding
Templates by themselves are useful. Polymer adds declarative, two-way data binding to templates. Data binding lets you assign, or bind, a JavaScript object as the template’s data model. A bound template can:
-
Maintain a single copy of the template’s contents (a template instance). The template instance is inserted in the DOM tree as a sibling of the original template.
-
Maintain a set of template instances for each item in an array, where each instance is bound to an item in the array.
-
Maintain two-way bindings inside each template instance between values in DOM nodes and the model data bound to the instance.
To see how this works, here’s an example Polymer element that uses data binding:
<polymer-element name="greeting-tag">
<!-- outermost template defines the element's shadow DOM -->
<template>
<ul>
<template repeat="{{s in salutations}}">
<li>{{s.what}}: <input type="text" value="{{s.who}}"></li>
</template>
</ul>
</template>
<script>
Polymer('greeting-tag', {
ready: function() {
// populate the element’s data model
// (the salutations array)
this.salutations = [
{what: 'Hello', who: 'World'},
{what: 'GoodBye', who: 'DOM APIs'},
{what: 'Hello', who: 'Declarative'},
{what: 'GoodBye', who: 'Imperative'}
];
}
});
</script>
</polymer-element>
As usual, this custom element includes an outer <template>
element to define its shadow DOM, as shown in Element declaration.
Inside that template, there’s a second template that contains
expressions surrounded by double-mustache {{
}}
symbols:
<template repeat="{{s in salutations}}">
<li>{{s.what}}: <input type="text" value="{{s.who}}"></li>
</template>
What’s going on in this template?
-
The
repeat="{{s in salutations}}"
tells the template to generate a DOM fragment (or instance) for each element in thesalutations
array. -
The contents of the template define what each instance looks like. In this case, it contains a
<li>
with a text node and an<input>
as its children. -
The expressions
{{s.what}}
and{{s.who}}
create data bindings to objects in thesalutations
array.
The values inside the {{
}}
are Polymer expressions. In the examples in this section, the expressions are either JavaScript objects (like salutations
) or paths (like s.who
). (Expressions can also include literal values and some operators –
see Expressions for details.)
When you create a <greeting-tag>
element, it initializes the salutations
array:
this.salutations = [
{what: 'Hello', who: 'World'},
{what: 'Goodbye', who: 'DOM APIs'},
{what: 'Hello', who: 'Declarative'},
{what: 'Goodbye', who: 'Imperative'}
];
Notice that this is just JavaScript data: there’s no need to import your data into special observable objects. The this.salutations
array serves as the model for the template.
The template is set in motion when you create or modify the model. Here’s the result:
and here’s what the DOM looks like:
You can see that the template created four instances immediately following its position in the document.
Dynamic, two-way data binding
Unlike server-side templating, Polymer data binding is dynamic. If you change a value in the model, the DOM observes the change and updates accordingly. The following sample adds a method to update the model. Press the button, and you can see the model data instantly reflected in the DOM.
<polymer-element name="greeting-tag">
<!-- outermost template defines the element's shadow DOM -->
<template>
<ul>
<template repeat="{{s in salutations}}">
<li>{{s.what}}: <input type="text" value="{{s.who}}"></li>
</template>
</ul>
<button on-click="{{updateModel}}">Update model</button>
</template>
<script src="greeting-tag.js"></script>
</polymer-element>
Polymer('greeting-tag', {
ready: function() {
this.salutations = [
{what: 'Hello', who: 'World'},
{what: 'Goodbye', who: 'DOM APIs'},
{what: 'Hello', who: 'Declarative'},
{what: 'Goodbye', who: 'Imperative'}
];
this.alternates = ['Hello', 'Hola', 'Howdy'];
this.current = 0;
},
updateModel: function() {
this.current = (this.current + 1) % this.alternates.length;
this.salutations[0].what = this.alternates[this.current];
}
});
<!DOCTYPE html>
<html>
<head>
<script src="webcomponents.min.js"></script>
<link rel="import" href="greeting-tag.html">
</head>
<body>
<greeting-tag></greeting-tag>
</body>
</html>
However, the DOM doesn’t just observe data in the model. When you bind a DOM element that collects user input, it pushes the collected value into the model.
Note: You can use change watchers and observe blocks to trigger custom behavior when the model data changes.
Lastly, see what happens when you add and remove items from the salutations
array:
The repeat
attribute ensures there is one instance for each item in the
array. We removed two elements from the middle of salutations
and inserted one in their place. The
<template>
responded by removing the two corresponding instances and creating a new one in the right location.
Getting the idea? Data binding allows you author your HTML using HTML which contains information about where data goes and directives which control the document’s structure – all depending on the data you provide it.
Event handling and data binding
With data binding, it’s easy to add event handlers using the declarative event mapping (on-event handlers):
<template>
<ul>
<template repeat="{{s in stories}}">
<li on-click="{{selectStory}}">{{s.headline}}</li>
</template>
</ul>
</template>
Often, you’ll want to identify the event with the model data used to generate the template instance, either to update the model data or to access a piece of data that isn’t rendered by the template.
You can get the model data from the event’s target.templateInstance.model
property. Any identifiers that you could access inside the template are
available as properties on the .model
object.
For example, the selectStory
method might look like this:
selectStory: function(e, detail, sender) {
var story = e.target.templateInstance.model.s;
console.log("Clicked " + story.headline);
this.loadStory(story.id); // accessing non-rendered data from the model
}
Continue on to: