laco0416です。Polymer 1.0からBootstrapのテーマを自作Custom Elementsに適用するのが簡単になった話をします。
0.5から1.0で変わったこと
前回の記事に書いたとおり、Polymer 0.5系におけるShadow DOMの実装はW3Cの仕様に忠実に、Shadow DOMの外からのCSSの侵入を防ぐようになっていました。しかしPolymer 1.x系では実装を軽量で高速な Shady DOM に切り替えたことで、CSSのスコープは内から外への漏洩を防ぐのみとなりました(W3Cの仕様からはズレますが…)。
この変更によりCustom Elements内でグローバルのスタイルを柔軟に適用できるようになったので、Bootstrapのようなテーマと共に使うのがごくごく自然な書き方で実現できます。
PolymerとBootstrapを組み合わせる意義
WebComponentsのないHTMLはスタイリングのためだけにdivネスト地獄を容易に引き起こします。例えば名前の部分だけスタイルを変えるネームタグを作る次のようなHTMLはセマンティックでない構造をしています
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
我々がやりたいことは「ネームタグを描画する」ことです。そしてそのために必要な情報だけをもつ構造は次の形です
<div id="nameTag">Bob</div>
これでは#nameTag
の要素にしか適用できないので、汎用的にDOM要素として配置できるようにするのがWebComponentsであり、Polymerです。
<name-tag name="Bob"></name-tag>
誰がどうみてもこのタグはネームタグに「Bob」を表示する要素ですね。
WebComponentsはこのように、divの入れ子構造による非セマンティックなスタイリングを取り払い、DOM構造に意味を持たせることが可能な技術です。
Bootstrapは御存知の通り、だれでも(デザインなんてわからないプログラマーでも!)簡単にレスポンシブで、それなりの見た目のWebページ、アプリケーションが作れるCSSテーマとJSのツールキットのセットです。そして、Bootstrapはとりわけdivネストが深くなりがちです。BootstrapとPolymerを組み合わせることで、少しでもセマンティックなindex.htmlを書くことは、三ヶ月後にそのコードを読む自分を少なからず幸せにするでしょう。
サンプル
Bootstrapにはいくつかのサンプルが用意されていますが、その中で今回は「Jumbotron」というサンプルの中にある小さなパーツをPolymerで作ってみました。
完成品がこちらの<row-item>
。ドキュメントからheader
とlink-to
を属性として指定し、コンテンツ部分は分散ノードとして取り込みました
<!DOCTYPE html>
<html>
<head lang="ja">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap with Polymer</title>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/>
<link rel="import" href="elements/row-item.html"/>
</head>
<body>
<div class="container">
<div class="row">
<row-item class="col-md-4" header="ヘッダーA" link-to="#A">
<p>Content of A</p>
</row-item>
<row-item class="col-md-4" header="ヘッダーB" link-to="#B">
<p>Content of B</p>
</row-item>
<row-item class="col-md-4" header="ヘッダーC" link-to="#C">
<p>Content of C</p>
</row-item>
</div>
</div>
</body>
</html>
準備
bowerで必要なパッケージをインストールします
> bower install --save polymer PolymerElements/paper-elements bootstrap
エントリポイントになるHTML(ドキュメント)でWebComponentsのPolyfillとBootstrapのCSSファイルを読み込みます。
<!DOCTYPE html>
<html>
<head lang="ja">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap with Polymer</title>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/>
</head>
<body>
</body>
</html>
コンポーネント作成
element/row-item.htmlを作成します。今回はドキュメントからBootstrapのスタイルが降ってくるので、コンポーネント内からBootstrapを呼び出す必要はありません。もちろん広く配布したいコンポーネントであればコンポーネント自体をBootstrapに依存させて<link>
でCSSファイルを読み込むことも可能です。(参考)
<link rel="import" href="../bower_components/polymer/polymer.html"/>
<link rel="import" href="../bower_components/paper-button/paper-button.html"/>
<dom-module id="row-item">
<template>
<h2>{{header}}</h2>
<content></content>
<paper-button class="btn btn-primary" on-click="goLink">View details »</paper-button>
</template>
<script>
Polymer({
is: 'row-item',
properties: {
header: {
type: String,
value: "Heading"
},
linkTo: {
type: String
}
},
goLink: function () {
window.location.href = this.linkTo;
}
});
</script>
</dom-module>
プロパティの宣言
コンポーネントを使用する側からAttributeとして値を渡すにはproperties
の中でプロパティを宣言する必要があります。
Polymer({
is: 'row-item',
properties: {
header: {
type: String,
value: "Heading"
},
linkTo: String
}
});
プロパティは名前をキーにオブジェクトもしくは型を指定します。オブジェクトを指定する場合もtype
プロパティは必須です。value
は初期値になります。
プロパティを文字列としてDOM中で展開するには{{}}
を使います
<h2>{{header}}</h2>
イベントハンドラ内で使う場合はthis
のプロパティとして呼び出せます
goLink: function () {
window.location.href = this.linkTo;
}
イベントリスナは通常のHTML要素と同じように記述できます。ハンドラーはPolymer()
に渡すオブジェクトのプロパティとして宣言します。
<paper-button class="btn btn-primary" on-click="goLink">View details »</paper-button>
Polymer({
is: 'row-item',
properties: {
header: {
type: String,
value: "Heading"
},
linkTo: {
type: String
}
},
goLink: function () {
window.location.href = this.linkTo;
}
});
CSSテーマの適用
ここまでで気づいた方もいるかと思いますが、paper-button
のクラスにbtn btn-primary
が指定されています。これはBootstrapで宣言されているボタン向けのスタイルです。
Bootstrap 3系はフラットデザインがベースになっているので、クリックした際のアニメーション等はありません。
この<row-item>
ではPolymerのPaper Elementsに含まれる<paper-button>
にこのクラスを設定しています。<paper-button>
はマテリアルデザインのボタンスタイルになっていて、クリックした場所から波紋のようにアニメーションが広がります。
今回<paper-button>
にbtn btn-primary
を設定したことで、<paper-button>
のアニメーションはそのままに、カラーリングやシェイプはBootstrapのbtn btn-primary
を適用することができました
配置
作成した<row-item>
をドキュメントに配置します。
<!DOCTYPE html>
<html>
<head lang="ja">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap with Polymer</title>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/>
<link rel="import" href="elements/row-item.html"/>
</head>
<body>
<div class="container">
<div class="row">
<row-item class="col-md-4" header="ヘッダーA" link-to="#A">
<p>Content of A</p>
</row-item>
<row-item class="col-md-4" header="ヘッダーB" link-to="#B">
<p>Content of B</p>
</row-item>
<row-item class="col-md-4" header="ヘッダーC" link-to="#C">
<p>Content of C</p>
</row-item>
</div>
</div>
</body>
</html>
<row-item>
も他のHTML要素と変わらず、row
とcol-md-4
でレスポンシブに表示することができます。
まとめ
Polymer 1.0のShady DOMのおかげで特にShadow DOMのCSSスコープがあることを感じずに簡単にコンポーネント中でBootstrapが使えました。BootstrapにかぎらずほとんどのCSSテーマが同様に使えると思います。既存のWebアプリケーションを部分的にでもPolymerでコンポーネント化してみてはどうでしょうか?