今年注目のオープンWebテクノロジーのひとつに、Web Componentsが挙げられると思います。HTML5Experts.jpでも今まで幾度も関連記事、Polymer.jsについての記事が紹介されてきました。今回は実際に、PolymerとMaterial Designのデザインコンセプトを用いて、視覚的にもユーザエクスペリエンスにも優れたチャットアプリを実際に作ってみましょう。
まず始める前にこのライブデモ、Kitteh Anonymousをデスクトップまたはモバイルのモダンブラウザで実際に試してみてください。ここでは、このデモの簡略版であるLiteバージョンを実際に作成する方法をステップ・バイ・ステップで紹介したいと思います。
必要な知識
泉水翔吾さんの記事、Web Componentsを簡単・便利にするライブラリ「Polymer」を使いこなそうや、佐藤歩さんの話題のMaterial DesignをWebで実現!Polymerで「Paper Elements」を試そうを先に読むのをおすすめします。
PolymerとWebスタンダード
ちまたに数多く存在する、JavaScript UI library。Polymerも単にまたそのひとつ、と思うかもしれません。しかしPolymerが他と違うのは、これがW3C WebプラットフォームプリミティブのWeb Componentsを基礎に作られていることです。このWeb Componentsファミリーに含まれるものに
などが挙げられます。
そしてPolymerに含まれる webcomponents.js は W3C DOM4のDOM mutation observersや ECMAScript standardsである Object.observe() のPolyfillの役目を果たしています。
レゴブロックのようにWebを構築しよう
Polymer webコンポーネントはカプセル化された何度も再利用できるコンポーネント。まるでレゴで家を構築するかのように、既存のパーツを使ったり自分で組み立てたパーツを使ったりと、いろいろなパーツを組み合わせてWebアプリを作ることができます。
実際にPolymerでアプリを作るには、まず必要な要素をインポートしてから使うことになります。
1 2 3 4 5 |
<!-- Import element --> <link rel="import" href="paper-fab"> ... <!-- Use element --> <paper-fab icon="send"></paper-fab> |
Material DesignとPolymerの関係
Material Designは、既存のどのスクリーンサイズやデバイスにでも対応できる、ビジュアルとインタラクションデザインにすぐれたデザインスペックで、もともとはAndroid 5.0 Lollipop用にデザインされたものが、のちにWeb用としてPolymerのPaper Elementsとして適応されるようにました。
1. Paper Elementsを使ってみよう
さて実際にこれらを使って自分のアプリを作ってみましょう。 まず、Polymerをインストール、そしてアプリ構築に必要なコンポーネントをインポートします。
1.1. Polymerをインストール
$ bower install --save Polymer/polymer
これで必要最低限のファイルがそろいます。
インストールが終わったら、index.htmlの <head> セクションに、 webcomponents.min.js のみをロードさせます。
1 2 3 4 5 6 7 |
<!DOCTYPE html> <html> <head> <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script> </head> <body> ... |
1.2. UI コンポーネントをインポート
このデモのUIには、いくつかのPolymer CoreとPaper Elements、そして自作のカスタム要素が使われています。
- core-scaffold (レスポンシブレイアウトを構成するheader、toolbar、menuなどがすでに揃った骨組み)
- core-item
- paper-input
- paper-fab
- カスタム (のちに作成しましょう)
Bowerを使って4つのcomponentsをインストールします。
1 2 3 4 |
$ bower install Polymer/core-scaffold $ bower install Polymer/core-item $ bower install Polymer/paper-input $ bower install Polymer/paper-fab |
インストールが終わったら、これらをHTML importsを使ってインポートします。
1 2 3 4 5 6 |
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script> <link rel="import" href="bower_components/core-scaffold/core-scaffold.html"> <link rel="import" href="bower_components/core-item/core-item.html"> <link rel="import" href="bower_components/paper-input/paper-input.html"> <link rel="import" href="bower_components/paper-fab/paper-fab.html"> |
このようにすることで、これらの要素がDOM上で使われる前に依存ファイルを含めて全てがしっかりロードされることになります。
1.3. ベーシックUIの構成
まず、 <core-scaffold> を使って、ベースとなるレイアウトを構成しましょう。この要素は、レスポンシブな骨組みを簡単に作る(scaffold)ことができる便利な要素で、サブコンポーネントとして <core-header-panel> 、 <core-toolbar> 、 <core-drawer-panel> などがすでに含まれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<body fullbleed unresolved> <core-scaffold> <!-- ドロウアーパネル --> <core-header-panel navigation flex> <core-toolbar class="tall"> <!-- an avatar and username will be here --> </core-toolbar> </core-header-panel> <!-- アプリのタイトル --> <div tool layout horizontal flex> <span flex>Kitteh Anonymous</span> <core-icon icon="account-circle"></core-icon> <span><!-- number of people online --></span> </div> <!-- メインコンテンツ --> <div flex> ... </div> </core-scaffold> </body> |
これでアプリのコア・ストラクチャーができました。 <core-scaffold> は実際はこの例よりもより簡単に使うとこができるのですが、このデモでは左のドロウアー部分のヘッダの高さをかえて、ベーシックスタイルよりも少し凝ったデザインにしています。
ここで <body> に見慣れない属性が使われていることにお気づきかと思います。 この fullbreed はbodyをビューポートにぴったり合わせるため、 unresolved はスタイライズされていないコンテンツが一瞬画面に表示される現象(FOUC)を防ぐために使われています。
他、要素の要所に flex 属性が使われています。PolymerはCSSスタンダードのFlexboxを属性として使っており、この子要素に layout horizontal | vertical が使われた時、この子要素が横または縦に、スクリーンにある分だけのスペースに引き伸ばされます。下の図を見てください。ヘッダUIのタイトル部分が引き伸ばされているため、右のアイコンと数字がきれいに右端に寄せて表示されています。
1.4. 個々のUI Elementsを使う
次に、このベースレイアウトの中に、先ほど一緒にインポートした個々のUIパーツを使います。 例えば次に示すコードサンプルでは、スクリーン横幅にあわせスタイライズされたインプットと、送信ボタンを表示しています。
二重カーリーブラケットに囲まれた {{input}} 、 {{sendMyMessage}}に注目してださい。これは式や on-で始まるイベントハンドラとして使われます。次のチャプターで簡単に説明します。
1 2 3 4 5 |
<!-- メインコンテンツ --> <div class="send-message" layout horizontal> <paper-input flex label="Type message..." id="input" value="{{input}}"></paper-input> <paper-fab icon="send" id="sendButton" on-tap="{{sendMyMessage}}"></paper-fab> </div> |
実際のアプリの中でコンポーネントがどのようにインポートされているかは、GitHub Repo上のlite.htmlのソースコードを見て確認してみてください。
1.5. データバインディング
PolymerはHTMLの新スタンダードである <template>を使って、宣言的で双方向のデータバインディングをサポートしています。 Polymerのデータバインディングにはいくつか方法があるのですが、ここでは <template> でアプリの全コードを囲むことによって自動バインディングを可能にしています。
1 2 3 4 5 6 7 |
<body fullbleed unresolved> <template is="auto-binding"> <core-scaffold> ... </core-scaffold> </template> </body> |
これで、前チャプター(1.4)で既出の paper-input の例にあるように、ユーザが入力する値を {{input}}を使って得ることができます。
1 2 |
var template = document.querySelector('template[is=auto-binding]'); doSomething(template.input); |
他、このデータモデルを使えば、要素を繰り返し使うようなマークアップを簡略化するなどということもできるのです。 実際にドロウアーパネルUIの中に、 core-itemを使ったリストを作成してみましょう。
1 2 3 |
<template repeat="{{item in items}}"> <core-item icon="{{item.icon}}" label="{{item.title}}"></core-item> </template> |
core-itemのコンテンツは、JavaScript側で、items配列を使ってオブジェクトで指定します。 ここでは、 <core-icons>で既に用意されているアイコンのセットを使っています。
1 2 3 4 5 |
template.items = [ {title: 'Uno', icon: 'cloud'}, {title: 'Dos', icon: 'polymer'}, {title: 'Tres', icon: 'favorite'} ]; |
これで、モデルを生成・変更した時に自動的にテンプレートにインスタンスが生成され、DOM上には下の図のように表示されます。
2. カスタム要素の作成
メインUIとなる部分にチャットの会話を表示させましょう。このUIパーツには、アバター、ユーザーID、チャットのテキストが表示させるようにしたいのですが、Polymer CoreにもPaperにもそういったバーツは存在しません。ですので自分でカスタム要素を作ってみましょう。
まず新規のHTMLファイルを作成します。ここではこのファイル名を、 x-chat-list.htmlとします。
このカスタム要素は、属性、 avatar、 color、 username、 textを扱います。
ここでは、カスタム要素の作製法を一から説明はしませんが、簡略化されたカスタム要素は次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<polymer-element name="x-chat-list" attributes="avatar color username text"> <template> <section class="user-list" layout horizontal> <div class="avatar {{color}}" style="background-image: url({{avatar}})"></div> <div flex> <div class="username">{{username}}</div> <div class="text">{{text}}</div> </div> </section> </template> <script> Polymer('x-chat-list', { // デフォルト値を指定 avatar: '', color: '', username: '', text: '' }); </script> </polymer-element> |
この例ではわかりやすく見せるために、 <style>...</style>部分が省略されていますが実際にUIのカスタム要素を作るにはCSSは不可欠でしょう。CSSを含む全ソースコードの x-chat-list.html はGitHub Repoで見てください。
できあがったら index.htmlに戻って、このカスタム要素をインポートします。
1 |
<link rel="import" href="x-chat-list.html"> |
1 2 3 4 5 6 7 8 |
<div flex class="chat-list"> <template repeat="{{message in messages}}"> <x-chat-list color="{{message.color}}" avatar="{{message.avatar}}" username="{{message.uuid}}" text="{{message.text}}"></x-chat-list> </template> </div> |
データは実際の生のチャットの会話を、PubNubデータ・ストリームサービスを使って表示させます。詳しくは次のセクションで説明します。
かなりはしょってしまいましたが、カスタム要素を作る詳しい説明は公式のドキュメントを参照してください。
3.PubNub Elementでメッセージの送信・受信をする
アプリのUI部分はすべて完成しましたので、今度はチャットルームそのものを作ってみましょう。データの送受信はPubNubが提供するリアルタイム・データストリームのPolymerエレメント版である、 <pubnub-element> を使います。 今まで使った要素と違うのはこの要素にはユーザインターフェイスが伴わないことです。では何をしてくれるのかというと、要素でカプセル化されたPubNub APIがクラウドでデータのpublish / subscribeのやりとりをはたしてくれるのです。なので私たちがサーバを立てる必要はありません。
<pubnub-element>はサードパーティAPIを使用しますので、まず自分のAPI keysを作成しておく必要があります。 PubNubアカウントはここから取得してください。短時間試してみたいだけならば、 publish_keyと publish_keyをdemoにして使うこともできます。
3.1. <pubnub-element>をインストール・インポート
インストールは、他の要素同様にBowerを使うことができます。
$ bower install --save pubnub-polymer
インストール後は index.htmlでこれをインポートします。
1 |
<link rel="import" href="bower_components/pubnub-polymer/pubnub-element.html"> |
3.2. <pubnub-element>を使う
まず、 <core-pubnub>を使ってクライアントの初期化をします。
1 2 |
<core-pubnub publish_key="your_pub_key" subscribe_key="your_sub_key" uuid="{{uuid}}"> </core-pubnub> |
ここで使われる uuidとはチャットルームの各ユーザのユニークIDで、ランダムな文字列を使います。このアプリでは、おのおののユーザがアクセスした時に、navy-siamese(濃紺色・シャム猫)のように、色と猫の種類の文字列の組み合わせで構成されたIDが与えられるようにします。
1 2 3 4 5 6 7 8 |
var randomColor = function() { var colors = ['navy', 'slate', 'olive',...]; return colors[(Math.random() * colors.length) >>> 0]; }; var randomCat = function() { ... }; template.uuid = randomColor() + '-' + randomCat(); |
3.3. メッセージの送信
<core-pubnub-publish>はチャンネルのサブスクライバー全てにメッセージを送信することができる要素です。
1 2 3 4 |
<core-pubnub publish_key="demo" subscribe_key="demo"> <core-pubnub-publish id="pub" channel="polymer-chat" message="Hello"> </core-pubnub-publish> </core-pubnub> |
では、ユーザが文字を入力して送信ボタン( <paper-fab> )をタップした時に、そのメッセージをサーバに送るコードを書きましょう。 Polymerは、 on-*イベントハンドラを使うので、この場合 on-tapイベントを用いて <paper-fab>がこのアクションを引き起こすことができるようにしてみます。
1 |
<paper-fab icon="send" on-tap="{{sendMyMessage}}"></paper-fab> |
1 2 3 4 5 6 7 8 9 10 11 |
template.sendMyMessage = function(e) { if(!template.input) return; // 入力フィールドが空の場合は何もしない template.$.pub.message = { uuid: uuid, avatar: avatarUrl, color: color, text: template.input }; template.$.pub.publish(); }; |
これでユーザの情報とメッセージテキストが、サーバに送られました。次はすべてのユーザがこれを受信して、内容ををDOMに表示させてみましょう。
3.4. 受信データのリアルタイムバインディング
ネットワークに送られたメッセージは <core-pubnub-subscribe>要素を使ってクライアント側で受信することができます。
1 2 3 4 5 |
<core-pubnub-subscribe channel="polymer-chat" id="sub" messages="{{messages}}" on-callback="{{subscribeCallback}}"> |
ここでの messages属性は受信されたメッセージオブジェクトの配列が含まれています。ではここで受け取ったデータを先ほど作成した <x-chat-list>カスタム要素を使って表示させましょう。 実は先のチャプター2で、既に messages 配列のインスタンスが使われていることに気がついたでしょうか? messages="{{messages}}" 属性が< core-pubnub-subscribe> と <x-chat-list>の間で双方向バインディングがすでに行われているのです。なので、自動的にDOM生成を行ってチャットの内容を表示することができるのです!
ではもう一度、 <x-chatlist>を見てみましょう。
1 2 3 4 5 6 7 8 9 |
<div flex class="chat-list"> <template repeat="{{message in messages}}"> <x-chat-list color="{{message.color}}" avatar="{{message.avatar}}" username="{{message.uuid}}" text="{{message.text}}"></x-chat-list> </template> </div> |
メッセージが受信された際には、 <core-pubnub-subscribe>のコールバック、 on-callbackが発生します。 このチャットの配列は時間順に並べられているので、新しいメッセージはスクリーンの一番下に表示されることになります。これではユーザの使い勝手が悪いので、コールバック発生時に自動的に最新チャットまでスクロールさせることにしましょう。
1 2 3 4 5 6 |
template.subscribeCallback = function(e) { template.async(function(){ var chatDiv = document.querySelector('.chat-list'); chatDiv.scrollTop = chatDiv.scrollHeight; // scroll to bottom }); }; |
これで、PolymerでMaterial Designの美しいデザインを使った簡単なチャットルームが完成しました!
このチュートリアルではわかりやすくするために、デモで使われているすべてのフィーチャーについてや、Shadow DOMのスタイリングについての説明は省略してあるので、さらに知りたい方は是非、GitHub のRepoで確認してみてください。
デモとチュートリアル、楽しんでいただけたなら幸いです!Happy coding!