You're viewing an older version of Polymer. Please see Polymer 2.0 for the latest.

Polymer provides a set of custom elements to help with common data binding use cases:

  • Template repeater. Creates an instance of the template's contents for each item in an array.
  • Array selector. Manages selection state for an array of structured data.
  • Conditional template. Stamps its contents if a given condition is true.
  • Auto-binding template. Allows data binding outside of a Polymer element.

The template repeater is a specialized template that binds to an array. It creates one instance of the template's contents for each item in the array. For each instance, it creates a new data binding scope that includes the following properties:

  • item. The array item used to create this instance.
  • index. The index of item in the array. (The index value changes if the array is sorted or filtered)

The template repeater is a type-extension custom element that extends the built-in <template> element, so it is written as <template is="dom-repeat">.

Example:

<dom-module id="employee-list">
  <template>

    <div> Employee list: </div>
    <template is="dom-repeat" items="{{employees}}">
        <div># <span>{{index}}</span></div>
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
    </template>

  </template>

  <script>
    Polymer({
      is: 'employee-list',
      ready: function() {
        this.employees = [
            {first: 'Bob', last: 'Smith'},
            {first: 'Sally', last: 'Johnson'},
            ...
        ];
      }
    });
  </script>

</dom-module>

Notifications for changes to item sub-properties are forwarded to the template instances, which update using the normal change notification events. If the items array is bound using two-way binding delimiters, changes to individual items can also flow upward.

Mutations to the items array itself (push, pop, splice, shift, unshift), should be performed using Polymer's array mutation methods. These methods ensure that the changes are observable by the data system. For more information on working with arrays, see Work with arrays.

When handling events generated by a dom-repeat template instance, you frequently want to map the element firing the event to the model data that generated that item.

When you add a declarative event handler inside the <dom-repeat> template, the repeater adds a model property to each event sent to the listener. The model object contains the scope data used to generate the template instance, so the item data is model.item:

<dom-module id="simple-menu">

  <template>
    <template is="dom-repeat" id="menu" items="{{menuItems}}">
        <div>
          <span>{{item.name}}</span>
          <span>{{item.ordered}}</span>
          <button on-click="order">Order</button>
        </div>
    </template>
  </template>

  <script>
    Polymer({
      is: 'simple-menu',
      ready: function() {
        this.menuItems = [
            { name: "Pizza", ordered: 0 },
            { name: "Pasta", ordered: 0 },
            { name: "Toast", ordered: 0 }
        ];
      },
      order: function(e) {
        var model = e.model;
        model.set('item.ordered', model.item.ordered+1);
      }
    });
  </script>

</dom-module>

The model is an instance of Polymer.Base, so set, get and the array manipulation methods are all available on the model object, and should be used to manipulate the model.

Note: The model property is not added for event listeners registered imperatively (using addEventListener), or listeners added to one of the <dom-repeat> template's parent nodes. In these cases, you can use the <dom-repeat> modelForElement method to retrieve the model data that generated a given element. (There are also corresponding itemForElement and indexForElement methods.)

To filter or sort the displayed items in your list, specify a filter or sort property on the dom-repeat (or both):

  • filter. Specifies a filter callback function, that takes a single argument (the item) and returns true to display the item, false to omit it. Note that this is similar to the standard Array filter API, but the callback only takes a single argument, the array item. For performance reasons, it doesn't include the index argument. See Filtering on array index for more information.
  • sort. Specifies a comparison function following the standard Array sort API.

In both cases, the value can be either a function object, or a string identifying a function defined on the host element.

By default, the filter and sort functions only run when one of the following occurs:

  • An observable change is made to the array (for example, by adding or removing items).
  • The filter or sort function is changed.

To re-run the filter or sort when an unrelated piece of data changes, call render. For example, if your element has a sortOrder property that changes how the sort function works, you can call render when sortOrder changes.

To re-run the filter or sort functions when certain sub-fields of items change, set the observe property to a space-separated list of item sub-fields that should cause the list to be re-filtered or re-sorted.

For example, for a dom-repeat with a filter of the following:

isEngineer: function(item) {
    return item.type == 'engineer' || item.manager.type == 'engineer';
}

Then the observe property should be configured as follows:

<template is="dom-repeat" items="{{employees}}"
    filter="isEngineer" observe="type manager.type">

Changing a manager.type field should now cause the list to be re-sorted:

this.set('employees.0.manager.type', 'engineer');

The observe property lets you specify item sub-properties to observe for filtering and sorting purposes. However, sometimes you want to dynamically change the sort or filter based on another unrelated value. In this case, you can use a computed binding to return a dynamic filter or sort function when one or more dependent properties changes.

<dom-module id="employee-search">

  <template>
    <input value="{{searchString::input}}">
    <template is="dom-repeat" items="{{employees}}" as="employee"
        filter="{{computeFilter(searchString)}}">
        <div>{{employee.lastname}}, {{employee.firstname}}</div>
    </template>
  </template>

  <script>
    Polymer({
      is: "employee-search",
      computeFilter: function(string) {
        if (!string) {
          // set filter to null to disable filtering
          return null;
        } else {
          // return a filter function for the current search string
          string = string.toLowerCase();
          return function(employee) {
            var first = employee.firstname.toLowerCase();
            var last = employee.lastname.toLowerCase();
            return (first.indexOf(string) != -1 ||
                last.indexOf(string) != -1);
          };
        }
      },
      properties: {
        employees: {
          type: Array,
          value: function() {
            return [
              { firstname: "Jack", lastname: "Aubrey" },
              { firstname: "Anne", lastname: "Elliot" },
              { firstname: "Stephen", lastname: "Maturin" },
              { firstname: "Emma", lastname: "Woodhouse" }
            ]
          }
        }
      }
    });
  </script>
</dom-module>

In this example, whenever the value of the searchString property changes, computeFilter is called to compute a new value for the filter property.

Because of the way Polymer tracks arrays internally, the array index isn't passed to the filter function. Looking up the array index for an item is an O(n) operation. Doing so in a filter function could have significant performance impact.

If you need to look up the array index and are willing to pay the performance penalty, you can use code like the following:

filter: function(item) {
  var index = this.items.indexOf(item);
  ...
}

The filter function is called with the dom-repeat as the this value, so you can access the original array as this.items and use it to look up the index.

This lookup returns the items index in the original array, which may not match the index of the array as displayed (filtered and sorted).

When nesting multiple dom-repeat templates, you may want to access data from a parent scope. Inside a dom-repeat, you can access any properties available to the parent scope unless they're hidden by a property in the current scope.

For example, the default item and index properties added by dom-repeat hide any similarly-named properties in a parent scope.

To access properties from nested dom-repeat templates, use the as attribute to assign a different name for the item property. Use the index-as attribute to assign a different name for the index property.

<div> Employee list: </div>
<template is="dom-repeat" items="{{employees}}" as="employee">
    <div>First name: <span>{{employee.first}}</span></div>
    <div>Last name: <span>{{employee.last}}</span></div>

    <div>Direct reports:</div>

    <template is="dom-repeat" items="{{employee.reports}}" as="report" index-as="report_no">
      <div><span>{{report_no}}</span>.
           <span>{{report.first}}</span> <span>{{report.last}}</span>
      </div>
    </template>
</template>

Call render to force a dom-repeat template to synchronously render any changes to its data. Normally changes are batched and rendered asynchronously. Synchronous rendering has a performance cost, but can be useful in a few scenarios:

  • For unit testing, to ensure items have rendered before checking the generated DOM.
  • To ensure a list of items have rendered before scrolling to a specific item.
  • To re-run the sort or filter functions when a piece of data changes outside the array (sort order or filter criteria, for example).

render only picks up observable changes such as those made with Polymer's array mutation methods. If you or a third-party library mutate the array without using Polymer's methods, you can do one of the following:

  • If you know the exact set of changes made to your array, use notifySplices to ensure that any elements watching the array are properly notified.
  • If you don't have an exact set of changes, you can Override dirty checking to force the data system to reevaluate the entire array.

For more information on working with arrays and the Polymer data system, see Work with arrays.

By default, dom-repeat tries to render all of the list items at once. If you try to use dom-repeat to render a very large list of items, the UI may freeze while it's rendering the list. If you encounter this problem, enable "chunked" rendering by setting initialCount. In chunked mode, dom-repeat renders initialCount items at first, then renders the rest of the items incrementally one chunk per animation frame. This lets the UI thread handle user input between chunks. You can keep track of how many items have been rendered with the renderedItemCount read-only property.

dom-repeat adjusts the number of items rendered in each chunk to try and maintain a target framerate. You can further tune rendering by setting targetFramerate.

You can also set a debounce time that must pass before a filter or sort function is re-run by setting the delay property.

Keeping structured data in sync requires that Polymer understand the path associations of data being bound. The array-selector element ensures path linkage when selecting specific items from an array.

The items property accepts an array of user data. Call select(item) and deselect(item) to update the selected property, which may be bound to other parts of the application. Any changes to sub-fields of the selected item(s) are kept in sync with items in the items array.

The array selector supports either single or multiple selection. When multi is false, selected is a property representing the last selected item. When multi is true, selected is an array of selected items.

<dom-module id="employee-list">

  <template>

    <div> Employee list: </div>
    <template is="dom-repeat" id="employeeList" items="{{employees}}">
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
        <button on-click="toggleSelection">Select</button>
    </template>

    <array-selector id="selector" items="{{employees}}" selected="{{selected}}" multi toggle></array-selector>

    <div> Selected employees: </div>
    <template is="dom-repeat" items="{{selected}}">
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
    </template>

  </template>

  <script>
    Polymer({
      is: 'employee-list',
      ready: function() {
        this.employees = [
            {first: 'Bob', last: 'Smith'},
            {first: 'Sally', last: 'Johnson'},
            ...
        ];
      },
      toggleSelection: function(e) {
        var item = this.$.employeeList.itemForElement(e.target);
        this.$.selector.select(item);
      }
    });
  </script>

</dom-module>

Elements can be conditionally stamped based on a boolean property by wrapping them in a custom HTMLTemplateElement type extension called dom-if. The dom-if template stamps its contents into the DOM only when its if property becomes truthy.

If the if property becomes falsy again, by default all stamped elements are hidden (but remain in the DOM tree). This provides faster performance should the if property become truthy again. To disable this behavior, set the restamp property to true. This results in slower if switching behavior as the elements are destroyed and re-stamped each time.

The following is a simple example to show how conditional templates work. Read below for guidance on recommended usage of conditional templates.

Example:

<dom-module id="user-page">

  <template>

    All users will see this:
    <div>{{user.name}}</div>

    <template is="dom-if" if="{{user.isAdmin}}">
      Only admins will see this.
      <div>{{user.secretAdminStuff}}</div>
    </template>

  </template>

  <script>
    Polymer({
      is: 'user-page',
      properties: {
        user: Object
      }
    });
  </script>

</dom-module>

Since it is generally much faster to hide and show elements rather than destroy and recreate them, conditional templates are only useful to save initial creation cost when the elements being stamped are relatively heavyweight and the conditional may rarely (or never) be true in given usages. Otherwise, liberal use of conditional templates can actually add significant runtime performance overhead.

Consider an app with 4 screens, plus an optional admin screen. If most users will use all 4 screens during normal use of the app, it is generally better to incur the cost of stamping those elements once at startup (where some app initialization time is expected) and simply hide/show the screens as the user navigates through the app, rather than destroy and re-create all the elements of each screen as the user navigates. Using a conditional template here may be a poor choice, since although it may save time at startup by stamping only the first screen, that saved time gets shifted to runtime latency for each user interaction, since the time to show the second screen will be slower as it must create the second screen from scratch rather than simply showing that screen. Hiding/showing elements is as simple as attribute-binding to the hidden attribute (e.g. <div hidden$="{{!shouldShow}}">), and does not require conditional templating at all.

However, using a conditional template may be appropriate in the case of an admin screen that's only shown to admin users of an app. Since most users aren't admins, there may be performance benefits to not burdening most of the users with the cost of stamping the elements for the admin page, especially if it is relatively heavyweight.

Polymer data binding is only available in templates that are managed by Polymer. So data binding works inside an element's local DOM template, but not for elements placed in the main document.

To use Polymer bindings without defining a new custom element, use the dom-bind element. This template immediately stamps its contents into the main document. Data bindings in an auto-binding template use the template itself as the binding scope.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="components/webcomponentsjs/webcomponents-lite.js"></script>
  <link rel="import" href="components/polymer/polymer.html">
  <link rel="import" href="components/iron-ajax/iron-ajax.html">

</head>
<body>

  <!-- Wrap elements with auto-binding template to -->
  <!-- allow use of Polymer bindings in main document -->
  <template id="t" is="dom-bind">

    <iron-ajax url="http://..." last-response="{{data}}" auto></iron-ajax>

    <template is="dom-repeat" items="{{data}}">
        <div><span>{{item.first}}</span> <span>{{item.last}}</span></div>
    </template>

  </template>

</body>
<script>
  var t = document.querySelector('#t');

  // The dom-change event signifies when the template has stamped its DOM.
  t.addEventListener('dom-change', function() {
    // auto-binding template is ready.
  });
</script>
</html>

All of the features in dom-bind are already available inside a Polymer element. Auto-binding templates should only be used outside of a Polymer element.

When one of the template helper elements updates the DOM tree, it fires a dom-change event.

In most cases, you should interact with the created DOM by changing the model data, not by interacting directly with the created nodes. For those cases where you need to access the nodes directly, you can use the dom-change event.