【Polymer 1.0】カスタムエレメントの登録とライフサイクル
目次
カスタムエレメントの登録
カスタムエレメントを登録するにはPolymer()
関数を使用します。この関数の引数にはプロトタイプを渡し、このプロトタイプに対して新しく作成するカスタムエレメントの設定を行います。プロトタイプはis
を持つ必要があり、これはカスタムエレメントのHTMLタグ名になります。なお仕様では、カスタムエレメントの名前に必ずダッシュ(-)を含む必要があります。
my-element.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <script> // カスタムエレメントの登録 MyElement = Polymer({ // isで指定した名前がカスタムエレメントのHTMLタグ名になる is: 'my-element', // この関数はライフサイクルコールバックの1つです created: function () { this.textContent = 'My Element!'; } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-element.html"> </head> <body> <script> document.addEventListener('WebComponentsReady', function () { // createElementでカスタムエレメントのインスタンスを作成 var myEl1 = document.createElement('my-element'); // カスタムエレメントのコンストラクタでインスタンスを作成 var myEl2 = new MyElement(); // 作成した2つのカスタムエレメントをbodyに追加 Polymer.dom(document.body).appendChild(myEl1); Polymer.dom(document.body).appendChild(myEl2); }); </script> </body> </html>
Polymer()
関数はブラウザへカスタムエレメントの登録を行い、そしてコンストラクタを返します。このコンストラクタでカスタムエレメントのインスタンスを作成できます。
またPolymer()
関数は引数で渡されたプロトタイプをもとにプロトタイプチェーンを設定し、カスタムエレメントとPolymerの基底プロトタイプであるPolymer.Base
を関連付けます。
カスタムコンストラクタの定義
Polymer()
関数を実行すると基本的なコンストラクタが返され、これを使用してカスタムエレメントをインスタンス化します。ただし基本的なコンストラクタには独自の引数を渡せないので、このような場合は引数のプロトタイプにfactoryImpl()
関数を指定します。
my-element.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <script> MyElement = Polymer({ is: 'my-element', // カスタムコンストラクタを定義し、独自の引数を受け取れるようにする factoryImpl: function (name, age) { this.name = name; this.age = age; }, // この関数はライフサイクルコールバックの1つです attached: function () { this.textContent = 'name=' + this.name + ', age=' + this.age; } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-element.html"> </head> <body> <script> document.addEventListener('WebComponentsReady', function () { // カスタムエレメントに独自の引数を渡し、インスタンスを作成 var myEl = new MyElement('taro', 18); // 作成したカスタムエレメントをbodyに追加 Polymer.dom(document.body).appendChild(myEl); }); </script> </body> </html>
Note:
Polymer()
関数から返されたコンストラクタを実行すると、内部ではdocument.createElement()
を使用してインスタンスを作成し、そのあとユーザーが指定したfactoryImpl()
関数を実行します。
カスタムコンストラクタの注意点:
factoryImpl()
関数はコンストラクタを使用してエレメントを作成した場合にのみ呼び出されます。HTMLタグ名を使用してマークアップで作成されたエレメント(<my-element></my-element>
のように)や、document.createElement()
を使用して作成されたエレメントの場合はfactoryImpl()
関数は呼び出されません。factoryImpl()
関数はエレメントが初期化された後(ready
コールバックの後)に呼び出されます。ready
コールバックについてはreadyコールバックとLocal DOMの初期化を参照ください。
ネイティブHTMLエレメントの継承
Polymerは現在ネイティブHTMLエレメントの継承のみをサポートしています。(<input>
、<button>
のようなエレメントの継承はサポートしますが、カスタムエレメントの継承はサポートしていません。ただし将来的にはサポートされる予定です)。これらのネイティブエレメントの拡張は
ネイティブHTMLエレメントを継承するには、Polymer()
関数のプロトタイプ引数にextends
を追加し、継承するエレメントのタグ名を指定します。
マークアップでis
属性を追加し、作成した型拡張カスタムエレメントの名前を指定します。
<input is="my-input">
次にサンプルプログラムを示します:
my-element.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <script> MyInput = Polymer({ is: 'my-input', // 継承するネイティブエレメントを指定 extends: 'input', created: function () { this.value = 'My input'; this.style.border = '1px solid red'; } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-input.html"> </head> <body> <!-- isアトリビュートを指定すること --> <input is="my-input"> <script> document.addEventListener('WebComponentsReady', function () { // createElementでカスタムエレメントのインスタンスを作成 var myInputEl1 = document.createElement('input', 'my-input'); console.log(myInputEl1 instanceof HTMLInputElement); // true // カスタムエレメントのコンストラクタでインスタンスを作成 var myInputEl2 = new MyInput(); console.log(myInputEl2 instanceof HTMLInputElement); // true }); </script> </body> </html>
ライフサイクルコールバック
Polymerの基底プロトタイプはいくつかのライフサイクルコールバックを実装しています。カスタムエレメント作成者はこれらコールバックのフック(あとから別のプログラムが処理を追加できるような仕組み)メソッドを実装して独自の処理を記述できます。
created ready attached detached attributeChanged
次のサンプルを実行してライフサイクルコールバックの実行順や動きを確認して見てください(これらの動作はデバッグログ出力されるので、ブラウザの開発ツールで確認して下さい)。
- 削除ボタン: カスタムエレメントを画面から削除
- 追加ボタン: カスタムエレメントを画面へ追加
- 属性値変更ボタン: カスタムエレメントのuser-name属性の値を変更
my-element.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <script> MyElement = Polymer({ is: 'my-element', properties: { userName: String }, created: function () { console.log(this.localName + ' was created'); }, ready: function () { console.log(this.localName + ' was ready'); }, attached: function () { console.log(this.localName + ' was attached'); }, detached: function () { console.log(this.localName + ' was detached'); }, attributeChanged: function (name, type) { console.log(this.localName + ' attribute ' + name + ' was changed to ' + this.getAttribute(name)); } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-element.html"> </head> <body> <div id="container"> <my-element user-name="taro">My Element!</my-element> </div> <div> <input type="button" value="削除" onclick="removeElOnClick()"> <input type="button" value="追加" onclick="addElOnClick()"> </div> <div> <input is="iron-input" id="userNameInput"> <input type="button" value="属性値変更" onclick="changeAttrOnClick()"> </div> <script> document.addEventListener('WebComponentsReady', function () { // 画面ロード時にカスタムエレメントを取得しておきます window.myEl = document.getElementsByTagName('my-element').item(0); }); // コンテナからカスタムエレメントを削除します function removeElOnClick() { var containerEl = document.getElementById('container'); Polymer.dom(containerEl).removeChild(window.myEl); } // コンテナにカスタムエレメントを追加します function addElOnClick() { var containerEl = document.getElementById('container'); Polymer.dom(containerEl).appendChild(window.myEl); } // カスタムエレメントのuser-name属性の値を変更します function changeAttrOnClick() { var userNameInputEl = document.getElementById('userNameInput'); Polymer.dom(window.myEl).setAttribute('userName', userNameInputEl.value); } </script> </body> </html>
readyコールバックとLocal DOMの初期化
ready
コールバックは
- カスタムエレメント内の
<template>
の認識 - Local DOM内の全てのエレメントの処理(バインディング、属性のデシリアライズ、デフォルト値設定…等々)
- Local DOM内の各エレメントが持つreadyコールバックを全て実行
のような処理が完了し、カスタムエレメント自身のLocal DOMの準備が整うと呼び出されます。
カスタムエレメント作成の際、Local DOMの内部を操作する必要がある場合はready
コールバックに実装を行ってください。
my-element.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <dom-module id="my-element"> <!-- Local DOM --> <template> <div>ヘッダー</div> <div> <!-- readyコールバックでここにタイトルが設定される --> <span id="contentTitle"></span> <!-- カスタムエレメント利用側で「name="content-value"」が設定されている場合、 そのカスタムエレメントのコンテンツがここに流し込まれる --> <content select="[name='content-value']"></content> </div> <div>フッター</div> </template> <script> Polymer({ is: "my-element", ready: function () { // Local DOM内にある「id="contentTitle"」のエレメントにタイトルを設定する this.$.contentTitle.textContent = 'お題は: '; } }); </script> </dom-module>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-element.html"> </head> <body> <my-element> <!-- Local DOMに流し込むコンテンツ --> <span name="content-value">readyコールバックとLocal DOMの初期化</span> </my-element> </body> </html>
初期化の順序
カスタムエレメントの基本的な初期化順は次のようになります:
created
コールバック- Local DOMの初期化
ready
コールバックfactoryImpl
コールバックattached
コールバック
上記の順序は保証されていますが、兄弟エレメントの初期化順は保証されていません。次の例でいうと<big-brother>
と<little-brother>
の初期化順は不動で、どちらが先に初期化が終了するか分かりません。
<!-- 親カスタムエレメント --> <my-parent> <!-- 兄カスタムエレメント --> <big-brother></big-brother> <!-- 弟カスタムエレメント --> <little-brother></little-brother> </my-parent>
また、親または兄弟エレメントにアクセスするタイミングには注意が必要です。というのも上記例でいうと<big-brother>
は初期化が終わっていても<my-parent>
と<little-brother>
の初期化が完了していないことがあるからです。もし<big-brother>
が<my-parent>
や<little-brother>
にアクセスしたい場合は、そのタイミングが重要になります。親または兄弟エレメントへのアクセスはattached
コールバックの中でasync
を呼び出し、そのコールバックの中で行ってください。
attached: function() { this.async(function() { // ここでなら親兄弟エレメントにアクセスできる }); }
次に初期化順に関するサンプルプログラムを示します:
family-elements.html
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <!-- 兄カスタムエレメント --> <dom-module id="big-brother"> <template> <p>{{myName}}</p> </template> <script> Polymer({ is: 'big-brother', properties: { myName: {type: String, value: '兄'} }, attached: function () { this.async(function () { // ここでなら親兄弟エレメントにアクセスできる var myParentEl = Polymer.dom(this).parentNode; var littleBrotherEl = Polymer.dom(this).nextElementSibling; console.log(myParentEl.myName + ', ' + this.myName + ', ' + littleBrotherEl.myName); }); } }); </script> </dom-module> <!-- 親カスタムエレメント --> <dom-module id="my-parent"> <template> <p>{{myName}}</p> <content></content> </template> <script> Polymer({ is: 'my-parent', properties: { myName: {type: String, value: '親'} } }); </script> </dom-module> <!-- 弟カスタムエレメント --> <dom-module id="little-brother"> <template> <p>{{myName}}</p> </template> <script> Polymer({ is: 'little-brother', properties: { myName: {type: String, value: '弟'} } }); </script> </dom-module>
index.html
<!DOCTYPE html> <html> <head> <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="family-elements.html"> </head> <body> <!-- 親カスタムエレメント --> <my-parent> <!-- 兄カスタムエレメント --> <big-brother></big-brother> <!-- 弟カスタムエレメント --> <little-brother></little-brother> </my-parent> </body> </html>
カスタムエレメントの属性
カスタムエレメントに独自のHTML属性を追加したい場合、Polymer()
関数のプロトタイプ引数にhostAttributes
を設定します。ここでの指定はキーが属性となり、値がその属性に設定される値となります。次の例でいうとstring-attribute
が属性となり'Value'
がその属性に設定される値となります。
属性にBoolean型の値を設定した場合について説明します。属性にBoolean型の値を設定するとカスタムエレメント利用側のマークアップに属性が出現する/しないという挙動になります。true
を設定すると利用側のマークアップに属性が出現しますがtrue
という値は設定されていません。false
を設定するとは属性自体が出現しません。
カスタムエレメントに属性を追加する例:
<script> Polymer({ is: 'x-custom', hostAttributes: { 'string-attribute': 'Value', 'boolean-attribute': true, 'tabindex': 0 } }); </script>
カスタムエレメント利用側のマークアップの結果:
<x-custom string-attribute="Value" boolean-attribute tabindex="0"></x-custom>
次はカスタムエレメントに属性を追加するサンプルプログラムです:
x-custom.html
<link rel="import" href="../../bower_components/polymer/polymer.html"> <script> Polymer({ is: 'x-custom', hostAttributes: { 'string-attribute': 'Value', 'boolean-attribute': true, 'tabindex': 0 } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="x-custom.html"> </head> <body> <x-custom></x-custom> </body> </html>
Class-styleコンストラクタ
カスタムエレメントのプロトタイプチェインの設定はしたいが、そのカスタムエレメントをすぐにはブラウザへ登録したくない場合、Polymer.Class()
関数を使用してください。Polymer.Class()
関数はPolymer()
関数と同じプロトタイプ引数を取り、プロトタイプチェインを設定しますが、ブラウザへの登録は行いません。Polymer.Class()
関数が返すコンストラクタはdocument.registerElement()
の引数に渡して実行することで、カスタムエレメントをブラウザに登録することができます。この後、このコンストラクタはカスタムエレメントをインスタンス化するのに使用することができます。
カスタムエレメントの定義と登録をワンステップで行いたい場合は、Polymer()
関数を使用してください。
my-element.html
<link rel="import" href="../../bower_components/polymer/polymer.html"> <script> // Class-styleコンストラクタを使用したカスタムエレメントを定義 var MyElement = Polymer.Class({ is: 'my-element', created: function () { this.textContent = 'My element!'; } }); </script>
index.html
<!DOCTYPE html> <html> <head> <script src="../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script> <link rel="import" href="my-element.html"> </head> <body> <script> document.addEventListener('WebComponentsReady', function () { // ブラウザへカスタムエレメントを登録 document.registerElement('my-element', MyElement); // この2つの記法は同じ意味 var myEl1 = new MyElement(); var myEl2 = document.createElement('my-element'); // 作成した2つのカスタムエレメントをbodyに追加 Polymer.dom(document.body).appendChild(myEl1); Polymer.dom(document.body).appendChild(myEl2); }); </script> </body> </html>