Polymerは、データバインディングの一般的なユースケースに備えて各種Custom Elementsを提供しています。:

  • テンプレートリピーター(dom-repeat):配列内のアイテムごとに、テンプレートのコンテンツを使ってインスタンスを生成します。
  • 配列セレクタ:構造化されたデータの配列において選択状態を管理します。
  • 条件付きテンプレート(dom-if):与えられた条件がtrueなら、そのコンテンツをスタンプします。
  • 自動バインディングテンプレート(dom-bind):Polymer Element外部でデータバインディングが利用できるようにします。

2.0向けのヒント:データバインディングのヘルパーエレメントは、下位バージョンと互換性のあるpolymer.htmlをインポートする際にバンドルされています。レガシーなインポートを利用しない場合、あなたが使用したヘルパーエレメントを個別にインポートする必要があります。

テンプレートリピーターは、配列へのバインドに特化したテンプレートです。配列内の各アイテムごとに、テンプレートのコンテンツを使いインスタンスを一つ生成します。各インスタンスは、以下のプロパティを含む新たなデータバインディングのスコープを生成します。:

  • item:インスタンスの生成に使用された配列のアイテム
  • index:配列内のitemのインデックス(配列がソートまたはフィルタリングされると、indexの値は変化します)

テンプレートリピーターを利用するには二つの方法があります。:

  • Polymer Element内またはPolymerが管理する他のテンプレートの内では<template is="dom-repeat>という省略記法を使用してください。

    <template is="dom-repeat" items="{{items}}">
    ...
    </template>
    
  • Polymerが管理するテンプレートの外側では、ラッパーエレメント<dom-repeat>を使用します。

    <dom-repeat>
      <template>
        ...
      </template>
    </dom-repeat>
    

    このフォームでは、通常、プロパティitemsを命令的に設定します。:

    var repeater = document.querySelector('dom-repeat');
    repeater.items = someArray;
    

Polymerが管理するテンプレートには、Polymer Elementのテンプレートや、dom-binddom-ifdom-repeatに属するテンプレート、あるいはTemplatizeライブラリによって管理されるテンプレートが含まれます。

ほとんどのケースにおいて、dom-repeatには、一番目(省略形)のフォームを使用することになるでしょう。

テンプレートリピーターは後方互換性を確保するため、レガシーなインポート(polymer.html)によって取り込まれます。もしpolymer.htmlをインポートしない場合は、次のコードに示すようdom-repeat.htmlを個別にインポートして下さい。

例:

<link rel="import" href="components/polymer/polymer-element.html">
<! -- import template repeater -->
<link rel="import" href="components/polymer/lib/elements/dom-repeat.html">

<dom-module id="x-custom">
  <template>

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

  </template>

  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          employees: {
            type: Array,
            value() {
              return [
                {first: 'Bob', last: 'Smith'},
                {first: 'Sally', last: 'Johnson'},
              ];
            }
          }
        }
      }

    }

    customElements.define(XCustom.is, XCustom);
  </script>

</dom-module>

itemのサブプロパティの変更通知は、テンプレートのインスタンスに転送され、通常の変更通知イベントを利用して更新されます。配列itemsが双方向バインディングデリミタを使ってバインドされている場合、個々のアイテムの変更は上に向けて流すこともできます。

テンプレートリピーターが変更を反映するには、配列itemsを監視できるように更新する必要があります。例えば:

// Use Polymer array mutation methods:
this.push('employees', {first: 'Diana', last: 'Villiers'});

// Use Polymer set method:
this.set('employees.2.last', 'Maturin');

// Use native methods followed by notifyPath
this.employees.push({first: 'Barret', last: 'Bonden'});
this.notifyPath('employees');

詳細については、オブジェクトと配列への変更を監視を参照してください。

dom-repeatテンプレートのインスタンスから生成されたイベントを処理する際、イベントが発生したエレメントと、アイテムを生成したモデルデータをマッピングしたいことがよくあるかもしれません。

<dom-repeat>テンプレートの内部に宣言型イベントハンドラを追加すると、リピーターはリスナーに送られてきた各イベントにmodelプロパティを付加します。modelオブジェクトには、テンプレートのインスタンスを生成するのに使用したスコープデータが含まれており、アイテムのデータはmodel.itemになります。

<link rel="import" href="polymer/polymer-element.html">
<link rel="import" href="polymer/lib/elements/dom-repeat.html">

<dom-module id="x-custom">

  <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>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          menuItems: {
            type: Array,
            value() {
              return [
                {name: 'Pizza', ordered: 0},
                {name: 'Pasta', ordered: 0},
                {name: 'Toast', ordered: 0}
              ];
            }
          }
        }
      }

      order(e) {
        e.model.set('item.ordered', e.model.item.ordered+1);
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>

</dom-module>

modelTemplateInstanceのインスタンスであり、Polymerのデータ関連のAPI:getsetsetPropertiesnotifyPathに加えて、各種配列変更メソッドを持っています。テンプレートのインスタンスに関連付けられたパスを用いれば、これらAPIをモデルの操作に利用できます。

例えば、上記のコードでは、ユーザーがピザの横にあるボタンをクリックすると、ハンドラは以下のコードを実行します。:

e.model.set('item.ordered', e.model.item.ordered+1);

これによって、item(この場合はピザ)の注文数を増やします。

modelオブジェクトでは、バインドされたデータのみ利用可能です。dom-repeat内部で、実際にバインドされたプロパティだけがmodelオブジェクトに追加されます。そのため場合によっては、イベントハンドラからプロパティへアクセスが必要な場合、テンプレート内のプロパティにバインドする必要があるかもしれません。例えば、ハンドラがproductIdプロパティにアクセスする必要がある場合、単にそのプロパティを表示に影響を与えないプロパティにバインドします。

  <template is="dom-repeat" items="{{products}}" as="product">
    <div product-id="[[product.productId]]">[[product.name]]</div>
  </template>

(addEventListenerを使って)命令的に登録されたリスナーや、特定のdom-repeatテンプレートの親ノードに設定されたリスナーに対して、modelプロパティが付加されることはありません。このようなケースでは、指定されたエレメントから生成されたモデルデータを取得するためにdom-repeatmodelForElementメソッドを利用できます。(またitemForElementindexForElementに相当するメソッドも存在します。)

表示されたリストのアイテムをフィルタリングまたはソートをするには、dom-repeatfilterまたはsort(あるいはその両方)プロパティを指定します:

  • filter:単一の引数(アイテム)をとるfilterコールバック関数を指定します。関数からの返り値がtrueならアイテムを表示して、falseなら省略します。これは標準のArrayfilterAPIに似ていますが、このコールバックは引数に一つの配列アイテムしか取らない点に注意してください。パフォーマンス上の理由から、引数にindexは含まれません。詳細については、配列インデックスによるフィルタリングを参照してください。
  • sort:標準のArraysortAPIに準じて比較関数を指定します。

いずれもその値は、関数オブジェクトでも、ホストエレメント上で定義された関数を指示する文字列でも構いません。

デフォルトでは、filter及びsort関数は、次のいずれかが発生した時だけ実行されます。

  • 配列に監視可能(observable)な変化が生じた。(例えば、アイテムの追加または削除によって)
  • filterまたはsort関数が変更された。

関連のないデータの一部が変更された時に、filtersortを再実行するにはrenderを呼び出してください。例えば、エレメントにsort関数の動作を変更するsortOrderプロパティがある場合、sortOrderに変更があったときにrenderを呼び出すことができます。

itemsの特定のサブフィールドに変更があった際に、filterまたはsort関数を再実行するには、itemサブフィールドのスペース区切りのリストにobserveプロパティを設定します。そうすることで、フィルタリングやソートが再実行されるでしょう。

例えば、dom-repeatで次のようなフィルター処理を行なったとします。:

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

この時、observeプロパティは次のように設定する必要があります。

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

manager.typeフィールドを変更すると、リストが再ソートされるはずです:

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

observeプロパティを使えば、指定したアイテムのサブプロパティをフィルタリングやソートのために監視できます。しかし、場合によっては、他の関連のない値に基づきフィルタやソートを動的に変更したいことがあるかもしれません。このような場合には、算出バインディングを使用し、依存部のプロパティが(一つ以上)変更された時点で、動的にフィルタまたはソート関数を返すことができます。

<dom-module id="x-custom">

  <template>
    <input value="{{searchString::input}}">

    <!-- computeFilter returns a new filter function whenever searchString changes -->
    <template is="dom-repeat" items="{{employees}}" as="employee"
        filter="{{computeFilter(searchString)}}">
        <div>{{employee.lastname}}, {{employee.firstname}}</div>
    </template>
  </template>

  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          employees: {
            type: Array,
            value() {
              return [
                { firstname: "Jack", lastname: "Aubrey" },
                { firstname: "Anne", lastname: "Elliot" },
                { firstname: "Stephen", lastname: "Maturin" },
                { firstname: "Emma", lastname: "Woodhouse" }
              ]
            }
          }
        }
      }

      computeFilter(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);
          };
        }
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>

この例では、searchStringプロパティの値が変更されるたびcomputeFilterが呼び出され、filterプロパティの新しい値を算出します。

Polymer内部における配列の記録法のために、配列のインデックスはフィルタ関数に渡されません。配列インデックスでアイテムを参照する際の計算量はO(n)です。これをフィルター関数上で実行ことを考えるとパフォーマンスにとても大きな影響が想定されます。

配列インデックスを参照する必要があり、パフォーマンス上の負荷を許容できる場合、次のようなコードを使用できます。:

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

フィルター関数はdom-repeatthisの値として呼び出されるので、this.itemsで元の配列にアクセスして、それをインデックスの参照に使用することができます。

この参照は、元の配列のアイテムインデックスを返します。このインデックスは、表示された(フィルターまたはソートされた)配列のインデックスとは一致しない可能性があります。

複数のdom-repeatテンプレートをネストした際、親のスコープからデータにアクセスしたいかもしれません。 dom-repeatの内部では、現在のスコープ内のプロパティによって隠蔽されない限り、親のスコープで利用可能なすべてのプロパティにアクセスできます。

例えば、dom-repeatによって追加されたデフォルトのitemindexプロパティは、親のスコープにある同名のプロパティを覆い隠します。

ネストされたdom-repeatテンプレートからプロパティにアクセスするには、as属性を使用してitemのプロパティに別の名前を割り当てます。indexプロパティに別の名前を割り当てるには、index-as属性を使用します。

<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>

renderを呼び出すことで、データへのどんな変更に対してもdom-repeatテンプレートのレンダリングが同期的に行われるよう強制します。通常、変更はバッチ処理で非同期にレンダリングされます。同期的レンダリングにはパフォーマンス上の負荷があるものの、いくつかのシナリオでは役立つでしょう。:

  • ユニットテストにおいて、生成されたDOMをチェックする前にアイテムがレンダリングされていることを保証する。
  • 特定のアイテムへスクロールする前に、アイテムのリストがレンダリングされていることを保証する。
  • データの一部が配列の外部で変更されたとき(例えば、ソート順序やフィルタ条件など)、sortfilter関数を再実行する。

renderは、Polymerの配列の変更メソッドによって発生するような監視可能(observable)な変化だけ検出します。

テンプレートが監視不能な変更を検出するようにするには、テンプレートを強制的に更新を参照してください 。

開発者やサードパーティーライブラリが、Polymerのメソッドを使用せず配列を変更する場合、次のいずれかを実行できます。:

  • 配列への変更をすべて正確に把握している場合は、notifySplicesを使用することで、配列を監視するすべてのエレメントに適切に通知されるようにします。

  • 配列のクローンを生成します。

    // Set items to a shallow clone of itself
    this.items = this.items.slice();
    

    データ構造が複雑な場合、深いクローン(deep clone)が必要になることがあります。

  • 変更箇所を正確に把握していない場合は、dom-repeatmutableDataプロパティを設定することで、配列へのダーティチェックを無効にできます。

    <template is="dom-repeat" items="{{items}}" mutable-data> ... </template>
    

    mutableDataセットを使用して、配列にnotifyPathを呼び出すと配列全体が再評価されます。

    //
    this.notifyPath('items');
    

    詳細については、[MutableDataミックスインの使用data-system#mutable-data)を参照してください。

配列やPolymerデータシステムとの連携に関する詳細は、配列との連携を参照してください。

デフォルトでは、dom-repeatは、一度にすべてのアイテムリストをレンダリングしようとします。非常に大きなアイテムリストのレンダリングにdom-repeat使用しようとすると、レンダリングの最中UIがフリーズするかもしれません。この問題に直面した場合は、initialCountを設定して「チャンクされた(chunked)」レンダリングを有効にします。チャンクモードでは、 dom-repeatは最初にinitialCountで指定されたアイテムをレンダリングし、残りのアイテムはアニメーションフレーム単位で、各チャンクを順番にレンダリングしていきます。これにより、UIスレッドはチャンクの間にユーザー入力を処理することができます。renderedItemCountプロパティ(読み取り専用)を使って、レンダリングが完了したアイテム数を追跡することもできます。

dom-repeatは、各チャンクでレンダリングされるアイテムの数を調整することで、ターゲットのフレームレートを維持します。またtargetFramerateの設定することでレンダリングを調整することもできます。

さらにdelayプロパティを設定することで、filtersort関数が再実行される前に、一定の経過時間(debounce time)を確保することもできます。

構造化されたデータを同期するには、バインドされたデータのパスの関係をPolymerが把握していなければいけません。array-selectorエレメントは、配列内から特定のアイテムが選択された際にパスの結合を保証してくれます。

itemsプロパティは、ユーザーデータの配列を受け取ります。select(item)deselect(item)を呼び出を呼び出すことで、アプリケーションの他の部分にリンクされている可能性のあるselectedプロパティを更新できます。selectedアイテムのサブフィールドへの変更は、配列items内のアイテムと同期が保たれます。

配列セレクタ(array selector)は、一つまたは複数の選択をサポートします。multifalseの場合、selectedプロパティは最後に選択したアイテムを表します。 multitrueの場合、selectedプロパティは選択されたアイテムの集合を表す配列になります。

配列セレクタは、後方互換性を確保するためにレガシー(polymer.html)インポートに含まれています。 polymer.htmlをインポートしない場合は、以下のコードに示すようにarray-selector.htmlをインポートしてください。

<link rel="import" href="components/polymer/polymer-element.html">
<! -- import template repeater -->
<link rel="import" href="components/polymer/lib/elements/dom-repeat.html">
<!-- import array selector -->
<link rel="import" href="components/polymer/lib/elements/array-selector.html">

<dom-module id="x-custom">

  <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>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          employees: {
            type: Array,
            value() {
              return [
                {first: 'Bob', last: 'Smith'},
                {first: 'Sally', last: 'Johnson'},
                // ...
              ];
            }
          }
        }
      }

      toggleSelection(e) {
        var item = this.$.employeeList.itemForElement(e.target);
        this.$.selector.select(item);
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>

</dom-module>

エレメントは、Boolenプロパティに基づいて条件付きでスタンプすることができます。これを実現するには、dom-ifと呼ばれる独自のHTMLTemplateElement型の拡張を使ってエレメントをラップします。dom-ifテンプレートは、そのifプロパティがtrueになった時だけそのコンテンツをDOM内にスタンプします。

ifプロパティが再びfalseになった場合、デフォルトでは、スタンプされたすべてのエレメントは非表示になります(ただし、DOMツリーには残ります)。この仕組みによって、ifプロパティが再びtrueになった際、より高速なパフォーマンスを実現します。この動作を無効にするには、restampプロパティをtrueに設定します。この場合、エレメントは毎回破棄され再スタンプされるので、ifによる切り替え動作は遅くなります。

条件付きテンプレートを使用する方法は二つあります。:

  • Polymer Elementまたは他のPolymerの管理するテンプレート内では、省略記法<template is="dom-repeat">を使用してください。

    <template is="dom-if" if="{{condition}}">
      ...
    </template>
    
  • Polymerの管理するテンプレートの外側では、ラッパーエレメント<dom-if>を使用します。

    <dom-if>
      <template>
        ...
      </template>
    </dom-if>
    

    このフォームでは、通常、itemsプロパティは命令的に設定します。:

    var conditional = document.querySelector('dom-if');
    conditional.if = true;
    

Polymerが管理するテンプレートには、Polymer Elementのテンプレートや、dom-binddom-ifdom-repeatに属するテンプレート、あるいはTemplatizerクラスによって管理されるテンプレートが含まれます。

ほとんどのケースにおいて、dom-repeatには、一番目(省略形)のフォームを使用することになるでしょう。

テンプレートリピーターは後方互換性を確保するため、レガシーなインポート(polymer.html)によって取り込まれます。もしpolymer.htmlをインポートしない場合は、次のコードに示すようarray-selector.htmlをインポートして下さい。

以下は、条件付きテンプレートがどのように動作するのかを示す簡単な例です。条件付きテンプレートの推奨された利用法は後述のガイダンスを参照してください。

例:

<link rel="import" href="components/polymer/polymer-element.html">
<! -- import conditional template -->
<link rel="import" href="components/polymer/lib/elements/dom-if.html">

<dom-module id="x-custom">

  <template>

    <!-- All users will see this -->
    <my-user-profile user="{{user}}"></my-user-profile>


    <template is="dom-if" if="{{user.isAdmin}}">
      <!-- Only admins will see this. -->
      <my-admin-panel user="{{user}}"></my-admin-panel>
    </template>

  </template>

  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          user: Object
        }
      }

    }

    customElements.define(XCustom.is, XCustom);
  </script>

</dom-module>

条件付きテンプレートを使用すると多少のオーバーヘッドが発生するため、CSSを用いれば容易に表示/非表示にできるような小さなUIエレメントには使用すべきでありません。

代わりに、読み込み時間を改善させたり、ページのメモリ容量を減らすために条件付きテンプレートを使って下さい。例えば:

  • ページ中のセクションをレイジーロードする。最初の描画時に必要のないページ中の一部エレメントは、dom-ifを使用してその定義が読み込みを終えるまで非表示にすることができます。この条件付きテンプレートの利用法に関しては、ケーススタディ:ショップアプリで解説しています。

  • 大規模サイトや複雑なサイトにおいてメモリの使用量を削減します。複雑なビューを複数持つシングルページアプリケーション(SPA)では、restampプロパティが設定されたdom-ifの中に各ビューを置くのは有効かもしれません。これにより、ユーザーが表示を切り替える(その箇所のDOMを再生成する)度に、ある程度のレイテンシは犠牲になりますが、メモリの使用効率が改善されます。

条件付きテンプレートをどんな場面で利用するかについて、どんな場合にも当てはまる画一的な指針はありません。サイトのプロファイリングは、条件付きテンプレートの効果的な使い所を把握するのに役立つでしょう。

Polymerのデータバインディングは、Polymerによって管理されたテンプレート内だけで利用できます。したがって、データバインディングは、エレメントのDOMテンプレート内(あるいはdom-repeatdom-ifテンプレート内)では動作しますが、メインドキュメントに置かれたエレメントでは機能しません。

新たにCustom Elementを定義することなくPolymerのバインディングを利用するには、<dom-bind>エレメントを使用します。このテンプレートは、その子のテンプレートの内容をメインドキュメントに即座に同期的にスタンプします。自動バインディングテンプレートによるデータバインディングは、バインディングスコープとして<dom-bind>エレメントそのものを利用します。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="components/webcomponentsjs/webcomponents-lite.js"></script>
  <link rel="import" href="polymer/lib/elements/dom-bind.html">
  <link rel="import" href="polymer/lib/elements/dom-repeat.html">

</head>
<body>
  <!-- Wrap elements with auto-binding template to -->
  <!-- allow use of Polymer bindings in main document -->
  <dom-bind>
    <template>

      <!-- Note the data property which gets sets below -->
      <template is="dom-repeat" items="{{data}}">
        <div>{{item.name}}: {{item.price}}</div>
      </template>

    </template>
  </dom-bind>
  <script>
    var autobind = document.querySelector('dom-bind');

    // set data property on dom-bind
    autobind.data = [
      { name: 'book', price: '$5.00'},
      { name: 'pencil', price: '$1.00'},
      { name: 'flux capacitor', price: '$8,000,000.00'}
    ];
  </script>
</body>
</html>

dom-bindの全ての機能は、Polymer Elementのであれば自動バインディングテンプレートを用いずとも利用できます。自動バインディングテンプレートは、Polymer Elementのでのみ利用すべきです。

注:Polymer 1.0では、 dom-bindが非同期にレンダリングされ、準備が済んだことを示すためにdom-changeイベントが発生しました。Polymer 2.0では、dom-bindは同期的にレンダリングされます。dom-changeイベントも発生しますが、エレメントが宣言された後でイベントハンドラがバインドされている場合、イベントを見逃すおそれがあります。

同期的レンダリングの強制dom-repeatと同様、dom-bindは、renderメソッドとmutableDataプロパティを提供しています。(同期レンダリングを強制テンプレートを更新で説明した通りです。)

テンプレートのヘルパーエレメントのどれかがDOMツリーを更新すると、dom-changeイベントが発生します。

ほとんどのケースおいては、生成したノードと直接やりとりするのではなく、モデルデータの変更を通じて生成したDOMとやりとりするべきです。ノードに直接アクセスする必要がある場合には、dom-changeイベントを使用することができます。