Master PG

プログラムし続ける...

【Polymer 1.0】テンプレートエレメント


目次

テンプレートリピーター (dom-repeat)

テンプレートリピーターは配列のバインディングに特化したテンプレートです。このテンプレートは、配列のアイテム毎にテンプレートコンテンツのインスタンスを作成します。また、各インスタンスのバインディングスコープには、次に示す2つのプロパティが追加されます:

  • item: テンプレートコンテンツのインスタンス作成に使用される配列のアイテム。
  • index: 配列のitemのインデックス (配列がソートまたはフィルタされるとindexは変化する)。

テンプレートリピーターは型拡張カスタムエレメントであり、ビルトインの<template>を拡張しています。使用するには<template is="dom-repeat">と記述します。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <div>社員リスト:</div>
    <br>
    <!-- テンプレートリピーターを使用する -->
    <template is="dom-repeat" items="{{employees}}">
      <div>
        <!-- 現在の配列アイテムのインデックス(index)を表示 -->
        <span># <span>{{index}}</span>. </span>
        <!-- 現在の配列アイテム(item)の内容を表示 -->
        <span>{{item.first}}</span> <span>{{item.last}}</span>
      </div>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {first: 'Bob', last: 'Smith'},
          {first: 'Sally', last: 'Johnson'},
          {first: 'Michael', last: 'Jackson'}
        ];
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111050p:plain


配列アイテムのサブプロパティを変更するには以下の方法で値を変更する必要があります。でないと値を変更してもテンプレートリピーターが生成したコンテンツが更新されません:

  • エレメントのプロパティバインディングを使用してサブプロパティの値を変更する。
  • Polymer.Baseset()メソッドを使用してサブプロパティの値を変更する。

サブプロパティ変更の詳細についてはここを参照ください。


配列自身の変更 (push, pop, splice, shift, unshift) はPolymerエレメントが提供するメソッドを使用しなければなりません。でないと配列自身の変更を行ってテンプレートリピーターが生成したコンテンツが更新されません。配列自身の変更の詳細についてはここを参照ください。


テンプレートリピーターのイベントハンドリング

dom-repeatテンプレートの各インスタンスをイベントハンドリングする際、どのエレメントでイベントが発火されたか、またそのエレメント (インスタンス) とひも付く配列のアイテムを取得したいことがあるでしょう。

dom-repeatテンプレート内のエレメントのイベントリスナはイベントアノテーションで設定できます。テンプレートリピーターは発生したイベントにmodelプロパティを追加し、これをイベントハンドラで取得できます。modelはテンプレートコンテンツのインスタンス生成時に関連付けられた配列アイテムを格納するモデルで、配列アイテムはイベントハンドラの中でevent.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="_orderOnClick">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ボタンがクリックされた際に呼び出される。
      _orderOnClick: function (event) {
        // テンプレートリピーターが追加した`model`を取得
        var model = event.model;
        // テンプレートリピーターのインスタンスとひも付く配列の`item`を取得
        var item = model.item;
        // `model.set()`を使用してオーダー数をインクリメント
        model.set('item.ordered', item.ordered + 1);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111128p:plain

このmodelPolymer.Baseを継承しているので、set()get()、配列操作のメソッド (push, pop, splice, shift, unshift) を全て使用することができます。というよりも、これらのメソッドを利用しないとテンプレートリピーターが生成したコンテンツが更新されないため、使用すべきです。


上記例のようにイベントアノテーションでリスナを設定するのではなく、addEventListener()でリスナの設定を行うと、イベントにmodelプロパティが追加されません。このような場合、dom-repeatテンプレートが提供するmodelForElement(el)メソッドを利用することでmodelを取得できます (これに関連したメソッドとしてitemForElement(el)indexForElement(el)も提供されます)。引数のelにはテンプレートコンテンツのインスタンス生成時に作成されたエレメント (<div>, <span>, <button>など) のいずれかを指定します。


次のサンプルでは、addEventListener()でリスナの設定を行っているため、modelForElement(el)modelを取得しています。引数のelにはevent.targetを渡しています。このevent.targetはテンプレートコンテンツのインスタンス生成時に作成された<button name="order">になります。

⇒ ソースコード

<dom-module id="simple-menu">
  <template>
    <template is="dom-repeat" id="menu" items="{{menuItems}}">
      <div>
        <span>{{item.name}}</span>
        <span>{{item.ordered}}</span>
        <button name="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}
        ];
      },
      attached: function () {
        this.async(function () {
          // テンプレートの各インスタンスがもつOrderボタンのイベントリスナを設定する
          var i, child;
          var menuChildren = Polymer.dom(this.root).children;
          for (i = 0; i < menuChildren.length; i++) {
            child = menuChildren[i];
            var button = Polymer.dom(child).querySelector('[name="order"]');
            if (!button) {
              continue;
            }
            // イベントアノテーションを使用せずaddEventListener()でリスナを設定
            button.addEventListener('click', this._orderOnClick.bind(this), false);
          }
        });
      },
      _orderOnClick: function (event) {
        // テンプレートリピーターが追加した`model`を取得
        var model = this.$.menu.modelForElement(event.target);
        // テンプレートリピーターのインスタンスとひも付く配列の`item`を取得
        var item = this.$.menu.itemForElement(event.target);
        // `model.set()`を使用してオーダー数をインクリメント
        model.set('item.ordered', item.ordered + 1);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111212p:plain


リストのフィルタとソート

表示されているリストをフィルタまたはソートするには、dom-repeatfilterまたはsortプロパティを指定してください (または両方):

  • filter: 標準のArray filter() APIに従ったフィルタコールバック関数を指定します。
  • sort: 標準のArray sort() APIに従ったフィルタコールバック関数を指定します。

両方とも設定する値には、関数オブジェクト、またはホストエレメントで定義した関数を文字列で指定できます。

デフォルトでは、指定されたフィルタとソート関数は配列自身が変更された場合にのみ実行されます (配列にアイテムが追加または削除されたような場合)。

配列アイテムのサブプロパティが変更された場合にフィルタとソート関数が実行されるためには、observerプロパティを指定します。このプロパティには配列アイテムのサブプロパティをスペース区切りで複数指定できます。こうすることで、指定された配列のサブプロパティが変更されるとフィルタとソート関数が実行されます。

次はdom-repeatfilterで指定する関数の例です:

// 管理者または技術者をフィルタリングするためのフィルタ関数
_filterFunc: function (employee) {
  return employee.admin || employee.engineer;
}

observerは次のように設定します:

<template is="dom-repeat" items="{{employees}}"
          filter="_filterFunc" observe="admin engineer">

指定されたフィルタ関数は、employee.adminまたはemployee.engineerが変更されると実行されます:

_adminOnChange: function (event) {
  ...
  event.model.set('item.admin', true);
}


次のサンプルでは、管理者または技術者のどちらかにチェックがある社員をフィルタして表示します。「管理者または技術者をフィルタ」のチェックボックスでフィルタの有り/無しを設定できます。また各社員の「管理者」と「技術者」のチェックを両方はずすと、この社員は画面から消えます。

(Note: このサンプルではフィルタの設定を上記例で示したようにdom-repeatのマークアップで行わず、_setupFilter()の中で行っています)

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <!-- フィルタ有り/無しのチェックボックス -->
    <div>
      <span>管理者または技術者をフィルタ: </span>
      <input id="filterCheckbox" type="checkbox" on-change="_setupFilter" checked>
    </div>
    <br>
    <!-- 社員リストのテンプレートリピーター -->
    <template is="dom-repeat" id="employeesRepeat" items="{{employees}}">
      <!-- 社員名 -->
      <div>名前: <span>{{item.name}}</span></div>
      <div>
        <!-- 管理者か否かのチェックボックス -->
        <span>管理者:
          <input type="checkbox" checked="{{item.admin}}"
            on-change="_adminOnChange">
        </span>
        <!-- 技術者か否かのチェックボックス -->
        <span>技術者:
          <input type="checkbox" checked="{{item.engineer}}"
            on-change="_engineerOnChange">
        </span>
      </div>
      <br>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Sally Johnson', admin: false, engineer: true},
          {name: 'Bob Smith', admin: true, engineer: false},
          {name: 'Michael Jackson', admin: true, engineer: true}
        ];
        this._setupFilter();
      },
      // テンプレートリピーターにフィルタ設定を行う関数
      _setupFilter: function () {
        if (this.$.filterCheckbox.checked) {
          // フィルタを設定(フィルタ関数を文字列で指定している)
          this.$.employeesRepeat.filter = '_filterFunc';
          this.$.employeesRepeat.observe = 'admin engineer';
        } else {
          // フィルタを解除
          this.$.employeesRepeat.filter = undefined;
        }
      },
      // 管理者または技術者をフィルタリングするためのフィルタ関数
      _filterFunc: function (employee) {
        return employee.admin || employee.engineer;
      },
      // 管理者のチェックが変更された場合のハンドラ
      _adminOnChange: function (event) {
        var admin = event.target.checked;
        event.model.set('item.admin', admin);
      },
      // 技術者のチェックが変更された場合のハンドラ
      _engineerOnChange: function (event) {
        var engineer = event.target.checked;
        event.model.set('item.engineer', engineer);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111258p:plain


次のサンプルでは、名前順で社員リストをソートします。「Sort name」のチェックボックスでフィルタの有り/無しを設定できます。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <div>
      <!-- ソート有り/無しのチェックボックス -->
      <span>Sort name: </span>
      <input id="sortCheckbox" type="checkbox" on-change="_setupSort" checked>
    </div>
    <br>
    <!-- 社員リストのテンプレートリピーター -->
    <template is="dom-repeat" id="employeesRepeat" items="{{employees}}">
      <!-- 社員名 -->
      <div>Name: <span>{{item.name}}</span></div>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Sally Johnson'},
          {name: 'Bob Smith'},
          {name: 'Michael Jackson'}
        ];
        this._setupSort();
      },
      // テンプレートリピーターにソート設定を行う関数
      _setupSort: function () {
        if (this.$.sortCheckbox.checked) {
          // ソートを設定(ソート関数を関数オブジェクトで指定している)
          this.$.employeesRepeat.sort = this._sortFunc;
          this.$.employeesRepeat.observe = 'name';
        } else {
          // ソートを解除
          this.$.employeesRepeat.sort = undefined;
        }
      },
      // 社員名で社員リストをソートするためのソート関数
      _sortFunc: function (employee1, employee2) {
        if (employee1.name < employee2.name) {
          return -1;
        } else if (employee1.name > employee2.name) {
          return 1;
        } else {
          return 0;
        }
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111334p:plain


ネストしたテンプレートリピーター

dom-repeatテンプレートがネストした場合、子スコープから親スコープのプロパティにアクセスしたいことがあります。親スコープのプロパティが子スコープのプロパティに上書きされるものは除いて、その他の親スコープのプロパティにはアクセスすることができます。

例えば、dom-repeatのスコープにはデフォルトでitemindexプロパティが追加されますが、子スコープでこれと同じ名前のプロパティがある場合、親スコープのプロパティは隠蔽されます。

このような場合は、as属性を使用してitemプロパティの別名をつけてください。indexプロパティはindex-as属性でプロパティの別名を付けてください。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <div>社員リスト:</div>
    <br>
    <!--
      社員リストのテンプレートリピーター
      ・as属性で`item`の別名を`employee`としている
    -->
    <template is="dom-repeat" items="{{employees}}" as="employee">
      <div>
        <!-- 社員リストのインデックス -->
        <span># <span>{{index}}</span>. </span>
        <!--
          社員名
          ・`item`ではなく別名の`employee`を使用している
        -->
        <span>{{employee.name}}</span>
      </div>
      <div>部下リスト:</div>
      <!--
        部下リストのテンプレートリピーター
        ・親スコープの`employee`にアクセスしている
        ・index-as属性で`index`の別名を`report_no`としている
      -->
      <template is="dom-repeat" items="{{employee.reports}}" index-as="report_no">
        <div>
          <!--
            部下リストのインデックス
            ・`index`ではなく別名の`report_no`を使用している
          -->
          <span># <span>{{report_no}}</span>. </span>
          <!-- 部下名 -->
          <span>{{item.name}}</span>
        </div>
      </template>
      <br/>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Taro Yamada', reports: [
            {name: 'Takeshi Okada'},
            {name: 'Torajiro Kuruma'}
          ]},
          {name: 'ichiro Suzuki', reports: [
            {name: 'Yoshio Hara'}
          ]}
        ];
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111422p:plain


配列セレクタ (array-selector)

<array-selector>エレメントは指定された配列の選択状態を管理してくれます。

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

itemsプロパティは選択状態を管理したい配列を指定します。選択状態は単一と複数があり、複数選択したい場合はmultiを指定します。選択状態をトグルさせたい場合はtoggleを指定します。

配列のアイテムが選択されると、selectedプロパティで指定した名前の変数に、選択されたアイテムが反映されます。selectedに指定した名前は他のエレメントと連携するために使用されます。下にあるサンプルではselectedに指定した_selectedEmployeesが「選択された社員リスト」のdom-repeatitemsにも設定されており、選択状態が変化するごとにこのリストも更新されます。

multifalseの場合、selectedは最後に選択されたアイテムとなります。multitrueの場合、selectedは選択されたアイテムの配列になります。


次のサンプルでは、Selectボタン付きで複数の社員が表示されます。Selectボタンをタップすると「選択された社員リスト」に選択された社員が現れます。もう一度Selectボタンをタップすると選択状態がトグルされて非選択になり、「選択された社員リスト」から社員が消えます。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <div>社員リスト:</div>
    <br>
    <!-- 社員リストのテンプレートリピーター -->
    <template is="dom-repeat" id="employeesRepeat" items="{{employees}}">
      <div>
        <!-- 社員の選択/解除のトグルボタン -->
        <button on-tap="_selectEmployeeOnTap">Select</button>
        <!-- 社員名 -->
        <span>{{item.name}}</span>
      </div>
    </template>
    <br>

    <!-- 社員の選択状態を管理する配列セレクタ -->
    <array-selector
      id="employeeSelector" items="{{employees}}"
      selected="{{_selectedEmployees}}" multi toggle>
    </array-selector>

    <!-- 選択された社員リストのテンプレートリピーター -->
    <div>選択された社員リスト:</div>
    <template is="dom-repeat" items="{{_selectedEmployees}}">
      <div>{{item.name}}</div>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Bob Smith'},
          {name: 'Sally Johnson'},
          {name: 'John Doe'}
        ];
      },
      _selectEmployeeOnTap: function (event) {
        // 取得した配列アイテムを選択状態をトグルする
        // (<array-selector>に`toggle`が設定されているのでトグルする)
        this.$.employeeSelector.select(event.model.item);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111458p:plain


条件付きテンプレート (dom-if)

ある条件の時にエレメントを表示させたい場合はdom-ifテンプレートを利用できます。dom-ifHTMLTemplateElement型拡張カスタムエレメントです。dom-ifで対象のエレメントを囲い、ifプロパティに指定した条件がtrueになった場合のみエレメントがDOMに挿入されます。

デフォルトではifプロパティがfalseになった場合、囲まれているエレメントが隠されます (ただしDOMツリーには残ります)。この挙動によりifプロパティが再度trueになった場合はエレメントの再生成が行われないためパフォーマンスが向上します。この挙動を無効にしたい場合はrestampプロパティをtrueに設定してください。この設定によりifプロパティの変更が起こるたびにエレメントの破棄と生成が行われるためパフォーマンスは低下します。

<dom-module id="employee-list">
  <template>
    ...
    <div>選択された社員:</div>
    <div>
      <span>{{_selectedEmployee.name}}</span>
      <!-- 選択された社員が管理者(adminがtrue)の場合のみ表示される -->
      <template is="dom-if" if="{{_selectedEmployee.admin}}">
        <span>(管理者)</span>
      </template>
    </div>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Bob Smith', admin: true},
          {name: 'Sally Johnson', admin: false},
          {name: 'John Doe', admin: true}
        ];
      },
      ...
    });
  </script>
</dom-module>


次のサンプルでは、Selectボタン付きで複数の社員が表示されます。Selectボタンをタップすると「選択された社員」に選択された社員が現れます。この社員が管理者の場合のみ「(管理者)」が表示されます。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <div>社員リスト:</div>
    <br>
    <!-- 社員リストのテンプレートリピーター -->
    <template is="dom-repeat" id="templateList" items="{{employees}}">
      <div>
        <!-- 社員の選択/解除のトグルボタン -->
        <button on-tap="_selectEmployeeOnTap">Select</button>
        <!-- 社員名 -->
        <span>{{item.name}}</span>
      </div>
    </template>
    <br>
    <!-- 社員の選択状態を管理する配列セレクタ -->
    <array-selector
      id="selector" items="{{employees}}" selected="{{_selectedEmployee}}">
    </array-selector>
    <!-- 選択された社員ー -->
    <div>選択された社員:</div>
    <div>
      <span>{{_selectedEmployee.name}}</span>
      <!-- 選択された社員が管理者(adminがtrue)の場合のみ表示される -->
      <template is="dom-if" if="{{_selectedEmployee.admin}}">
        <span>(管理者)</span>
      </template>
    </div>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      ready: function () {
        this.employees = [
          {name: 'Bob Smith', admin: true},
          {name: 'Sally Johnson', admin: false},
          {name: 'John Doe', admin: true}
        ];
      },
      _selectEmployeeOnTap: function (e) {
        // エレメントと関連する配列アイテムを取得
        var item = this.$.templateList.itemForElement(e.target);
        // 取得した配列アイテムを選択状態にする
        this.$.selector.select(item);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111532p:plain


条件付きテンプレートの適切な利用ケース

条件付きテンプレートは条件を満たさないとエレメントが生成されないため、初期起動のコストは軽減されます。ただし、条件がめったにtrueにならない場合にのみ条件付きテンプレートは利用すべきです。あまりに自由に条件付きテンプレートを使用すると実行時に重大なパフォーマンスのオーバーヘッドを引き起こします。

例として、アプリケーションに一般ユーザーが使用する4つの画面 (タブなどで切り替える) と、管理者が使用する管理画面があることを思い浮かべてみてください。条件付きテンプレートを利用して、起動時は初期画面しか生成せず、あとの画面は生成しないようにしたとします。確かに起動は早くなるかもしれませんが、この削減されたコストは別の画面を表示する際のコストへ移動しただけです。これにより画面を切り替える際に画面を生成しなければならないため、単純に画面を切り替えるのにくらべて表示が遅くなってしまいます。シンプルな画面の切替はhidden属性をバインドすることで行えます (例: <div hidden$=""> 属性バインディングの詳細はここを参照)。

ただし、管理者のみが利用できる管理画面に対して条件付きテンプレートを使用することは適切です。ほとんどのユーザーは管理者ではなく、管理画面を開けないため画面切り替えのパフォーマンス低下を受けることはありません。管理画面が比較的重い場合は条件付きテンプレートの使用が特に有効になります。


自動バインディングテンプレート (dom-bind)

Polymerのデータバインディングは、カスタムエレメントのLocal DOMテンプレートの中でのみ動作します。

メインドキュメントなどで、新しいカスタムエレメントを定義せずにPolymerのバインディング使用するには、自動バインディングテンプレート (dom-bind) を使用してください。自動バインディングテンプレートのバインディングスコープは、自動バインディングテンプレート自身になります。

⇒ ソースコード

<!DOCTYPE html>
<html>
<head>
  <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="../bower_components/polymer/polymer.html">
</head>
<body>

<!--
  メインドキュメントでPolymerのバインディングを使用するには、
  自動バインディングテンプレートでバインディングしたいエレメントを囲んでください
-->
<template id="employeeList" is="dom-bind">
  <template is="dom-repeat" items="{{employees}}">
    <div><span>{{item.name}}</span></div>
  </template>
</template>

<script>
  // 自動バインディングテンプレートを取得
  var employeeList = document.querySelector('#employeeList');

  // テンプレートがDOMツリーを更新するとdom-changeイベントが通知されます
  employeeList.addEventListener('dom-change', function () {
    // この時点で自動バインディングテンプレートの準備は完了している
  });

  // Web Componentsの準備が整った際(画面の準備がととのった際)の処理
  document.addEventListener('WebComponentsReady', function () {
    // 自動バインディングテンプレートにデータを設定
    employeeList.employees = [
      {name: 'Bob Smith'},
      {name: 'Sally Johnson'},
      {name: 'John Doe'}
    ];
  });
</script>

</body>
</html>

f:id:masterpg:20160923111609p:plain

自動バインディングテンプレートはPolymerエレメントでも利用可能ですが、自動バインディングテンプレートはPolymerエレメントの外で使用すべきです。


dom-changeイベント

テンプレートエレメント (<template>) がDOMツリーを更新すると、dom-changeイベントが発火されます。

次のサンプルでは、社員リストの先頭にあるチェックボックスを変更するとdom-changeイベントに対するハンドラ (_onDomChange()) が呼び出されます。イベントが発生する仕組みや、イベントオブジェクトからどのような情報が取得できるか確認してみてください。

⇒ ソースコード

<dom-module id="employee-list">
  <template>
    <template is="dom-repeat" id="templateList" items="{{employees}}">
      <div>
        <!-- 管理者チェックボックス -->
        <input type="checkbox" checked="{{item.admin::change}}">
        <!-- 社員名 -->
        <span>{{item.name}}</span>
        <!-- 管理者ラベル -->
        <template is="dom-if" if="{{item.admin}}">
          <span>(管理者)</span>
        </template>
      </div>
    </template>
  </template>
  <script>
    Polymer({
      is: 'employee-list',
      listeners: {
        'dom-change': '_onDomChange'
      },
      ready: function () {
        this.employees = [
          {name: 'Bob Smith', admin: true},
          {name: 'Sally Johnson', admin: false},
          {name: 'John Doe', admin: true}
        ];
      },
      // ホスト内のいずれかのテンプレートエレメントがDOMツリーを更新すると
      // この`dom-change`イベントハンドラが呼び出されます。
      _onDomChange: function (event) {
        console.log('■■■ rootTarget  ■■■');
        console.log(Polymer.dom(event).rootTarget);
        console.log('■■■ localTarget ■■■');
        console.log(Polymer.dom(event).localTarget);
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923111646p:plain