HTML5 コンポーネント: 標準コンポーネントの実装

HTML5 の標準 API とオープンソースのポリフィルを使用する

この連載では、HTML5 のエキスパートである David Geary が HTML5 コンポーネントの実装方法を説明します。連載の最終回となる今回の記事では、Polymer と Mozilla X-Tag というプロジェクトを利用して、新しく誕生した HTML5 コンポーネント仕様に従ってコンポーネントを実装する方法を説明します。

David Geary, Author and speaker, Clarity Training, Inc.

David GearyCore HTML5 Canvas』の著者、David Geary は HTML5 Denver User's Group の共同設立者でもあり、Swing と JavaServer Faces に関するベストセラーの本を含め、Java に関する 8 冊の本の著者でもあります。また彼は、JavaOne、Devoxx、Strange Loop、NDC、OSCON などのカンファレンスで頻繁に講演を行っており、JavaOne Rock Star にも 3 度選ばれています。彼は developerWorks の連載記事、「HTML5 による 2D ゲームの開発」、「JSF 2 の魅力」、そして「GWT の魅力」の著者でもあります。



2013年 8月 29日

この連載について

HTML5 の標準 UI コンポーネント・モデルは今でも進化を続けています。この連載では、HTML5 のエキスパートである David Geary が、既存の技術を使用して特定の目的のための独自の HTML5 コンポーネントを作成する方法、そして正規の HTML5 コンポーネント・システムを定義する途上にある仕様を利用する方法を説明します。

この全 3 回からなる連載の最初の 2 回の記事では、特定の目的のための HTML5 スライダー・コンポーネントを実装する方法を説明しました。そのコンポーネントには、多くの高度な機能が備わっていて、例えばユーザーがスライダーのレールをクリックすると、CSS3 トランジションによってスライダーのノブが滑らかに移動します。けれども (連載の第 1 回の記事から明らかなように) 開発者の観点からすると、このコンポーネントをアプリケーションの中で組み込み HTML 要素のように簡単に使用することはできません。

具体的に言うと、この特定の目的のためのスライダーを使用するには、以下の作業が必要になります。

  1. 空の DIV を HTML に追加する。
  2. 新規の COREHTML5.Slider を作成する。
  3. ステップ 2 で作成したスライダーをステップ 1 で作成した空の DIV に追加する。
  4. スライダーにコントロールを接続するためのイベント・ハンドラーを実装する。

この特定の目的のためのスライダーは使いにくい上に、悪用されやすかったり、(意図的であるどうかにかかわらず) 乱用されやすかったりします。このスライダーは、スライダーを構成する要素を DOM ツリーに直接追加するため、特定の目的を持った最近のほぼすべての JavaScript コンポーネントと同じく、誰もがそれらの要素にアクセスできてしまうからです。

特定の目的のためのコンポーネント

特定の目的のためのコンポーネントとは、コンポーネントの実装に関する標準 API に従わないコンポーネントのことです。

前述のステップを減らして、ただ単にスライダー・タグを HTML に追加すれば済むようにできれば理想的です。これこそがまさにこの記事で行おうとしていることで、そのために以下の API を使用して、特定の目的のためのスライダーを HTML5 の標準コンポーネントに変身させます。

  • Shadow DOM: Shadow DOM API は、通常の方法ではアクセスできない要素 (document.getElementById() など) を DOM ツリーに追加できるようにします。Shadow DOM に含まれる要素はシャドー (影) の中に隠されるため、実質的に DOM ツリーの他の部分からは見えません。さらに、これらの要素はデフォルトでは周囲の文書に含まれる CSS による影響を受けないようになっています。
  • テンプレート: HTML5 テンプレートは、非アクティブな文書フラグメントです。非アクティブなフラグメントを表示する必要があるときには、テンプレートをスタンプアウト (作成するという意味) することによって、フラグメントを DOM ツリーでアクティブにします。テンプレートを使用することで、HTML、JavaScript、CSS をスタンプアウトしてカスタマイズできるようにカプセル化することができます。
  • カスタム要素: カスタム要素とは、独自に作成したカスタム・コンポーネントのタグのことです。カスタム・コンポーネントのタグには (例えば、<custom-tag> のように) ハイフンが含まれていなければならないという点を除けば、組み込み HTML タグと見分けがつきません。
  • HTML インポート: HTML インポートによって、ある HTML ファイルを別の HTML ファイルからインポートすることができます。これにより、独自のファイル内にカスタム・コンポーネントを実装することができます。

以上の API は、今でもなお仕様の策定が行われています。この記事を執筆している時点では、これらの API をすべてサポートしているブラウザーはありません。そのため、現時点で HTML5 の標準コンポーネントの実装を開始するには、欠けている機能を提供する 2 つのオープンソース・プロジェクトのいずれかを選択することができます。その 2 つのプロジェクトとは、Polymer と X-Tag です。

この記事では、以下の内容を説明します。

  • コンポーネント・マークアップを Shadow DOM にカプセル化する方法。
  • 非アクティブな HTML テンプレートからライブ DOM 要素をスタンプアウトする方法。
  • カスタム要素を作成して、それを JavaScript に接続する方法。
  • HTML ファイルをインポートする方法。
  • Polymer を使用してコンポーネントを実装する方法。
  • X-Tag を使用してコンポーネントを実装する方法。

使用できるブラウザーは Chrome だけです

この記事を執筆している時点で、Shadow DOM の例が機能するのは、最新バージョンの Chrome ブラウザーだけです。

Shadow DOM

図 1 に、動画を再生している HTML5 アプリケーションと、video 要素に含まれる要素を表示する Chrome の Elements インスペクター (「Tools (ツール)」 -> 「Developer tools (デベロッパー ツール)」の順にメニューを進むと選択できます) を示します。

図 1. video 要素の中身を表示する
動画の上に Elements インスペクターを重ねて表示している Chrome のスクリーン・キャプチャー

video 要素には、再生/一時停止ボタン、プログレス・インジケーター、その他の要素が含まれていることが明らかであるにも関わらず、Elements インスペクターには video 要素内に 1 つの source 要素が表示されているのみであることに注目してください。他の要素が表示されない理由は、それらの要素が Shadow DOM 内にあり、デフォルトで非表示になっているためです。図 2 に、Chrome の Elements インスペクターに Shadow DOM 要素を表示する方法を示します。

図 2. Chrome で Shadow DOM を有効にする
Chrome の Developer Tools (デベロッパー ツール) の設定ダイアログのスクリーン・キャプチャー。このダイアログで「Show Shadow DOM (Shadow DOM を表示)」チェック・ボックスにチェック・マークを付けると、Chrome に Shadow DOM が表示されるようになります。

図 2 に示す設定ダイアログを開くには、Chrome の「Developer Tools (デベロッパー ツール)」ウィンドウの右下隅にあるギア・アイコンをクリックします。設定ダイアログで「Show Shadow DOM (Shadow DOM を表示)」チェック・ボックスまでスクロールダウンしてください。このチェック・ボックスにチェック・マークを付けると、Chrome の Elements インスペクターには Shadow DOM に含まれる要素が表示されるようになります (図 3 を参照)。

図 3. video 要素の Shadow DOM を確認する
video 要素の Shadow DOM を表示する Chrome の Elements インスペクターのスクリーン・キャプチャー

図 3 には、図 1 と同じように video 要素に含まれる要素が表示されていますが、Shadow DOM 要素を表示するように Chrome が設定されているため、video 要素の内部構造が表示されています。

最近までブラウザーの実装者のみが使用可能であった Shadow DOM は、アプリケーション開発者にとっても有用であることが明らかになっています。それは、Shadow DOM 要素はデフォルトで周囲の DOM ツリーからアクセス不可能であり、周囲の DOM ツリーに影響されないためです。基本的に、これらの要素は独自の世界にカプセル化されているため、他のコンポーネントによって動作が干渉されることはありません。

要素を Shadow DOM 内に組み込む方法を説明するために作成したサンプル・コードは、図 4 に示す結果となります。

図 4. ボタンのシャドー・ルートの変更
ボタンのシャドー・ルートが変更されたアプリケーションのスクリーン・キャプチャー

このアプリケーションは、最初は図 4 の左側のスクリーン・キャプチャーのように表示されます。つまり、表示されるのはタイトルとボタンだけですが、ボタンをクリックすると、ボタンのテキストが写真とキャプションに置き換えられます (右側のスクリーン・キャプチャーを参照)。これらの写真とキャプションは、ボタンの Shadow DOM 内に存在します。

リスト 1 に、図 4 に示したアプリケーションの HTML を記載します。

リスト 1. ボタンの Shadow DOM を取り込む
<!DOCTYPE html>
<html>
   <head>
      <title>Shadow DOM</title>

      <style>
        button {
           font: 18px Century Schoolbook;
           border: thin solid gray;
           background: rgba(200, 200, 200, 0.5);
           padding: 10px;
        }
      </style>
   </head>

   <body>
      <h1>Shadow DOM</h1>

      <button id='button'>Click this button</button>

      <script>
         var b = document.getElementById('button');

         b.onclick = function (e) {
            var sr = b.webkitCreateShadowRoot();

            sr.innerHTML = '<p>This content is in the shadow DOM</p>' +
                            '<img src="beach.png">';
         };
      </script>
   </body>
</html>

アプリケーションがブラウザーにロードされるときに、リスト 1 の下部にある JavaScript が実行されて、 onclick イベント・ハンドラーが作成されます。このイベント・ハンドラーは webkitCreateShadowRoot() メソッドを使用してシャドー・ルートを作成します。シャドー・ルートとはつまり、Shadow DOM ツリーの最上位ノードです。イベント・ハンドラーは、この作成したシャドー・ルートに段落と画像の 2 つの要素を取り込みます。

Shadow DOM が可能にするカプセル化

Shadow DOM は、カスタム・コンポーネントに対して、オブジェクト指向プログラミングの最も基本的な教えであるカプセル化を提供します。コンポーネント開発者は、Shadow DOM を利用することで、作成するコンポーネントの内部構造を CSS スタイルやプログラムによるアクセスという形での外部の干渉から保護することができます。この保護機能は、コンポーネントが安全に相互運用できることを確実にする上で不可欠です。

図 4 のアプリケーションに示されているように、ある要素のシャドー・ルートを作成して、その要素の内部 HTML を設定すると、その要素の元の内容は必ず上書きされます。

ボタンはデフォルトで Shadow DOM を持たないため、ブラウザーはリスト 1 に示されているように Shadow DOM を作成することを許容します。しかし、 Shadow DOM をデフォルトで持つ要素 (video 要素など) に対しては、ブラウザーはあまり寛容ではありません。図 5 に示すように、sr = b.webkitCreateShadowRoot(); が原因で、Chrome の Elements インスペクターは「A Node was inserted somewhere it doesn't belong (ノードが関係のない場所に挿入されました)」というエラー・メッセージを出します。

図 5. video 要素のシャドー・ルートを変更しようとした場合 (そして失敗した場合)
video 要素の Shadow DOM を作成しようとしたことが原因のエラー・メッセージを表示する、Chrome Element インスペクターのスクリーン・キャプチャー

テンプレート、カスタム要素、HTML インポート

ここまでで実際に Shadow DOM が使用されているところを見たので、今度は Shadow DOM を HTML5 Web Components に不可欠な他の要素 (テンプレート、カスタム要素、HTML インポート) と一緒に使用する実用的な使い方の例を説明します。

図 6 に、HTML5 Denver Meetup グループが開催する会合の単純な順不同リストを示します。

図 6. 会合の順不同リスト
会合の順不同リストのスクリーン・キャプチャー

図 6 の順不同リストを作成するマークアップは、リスト 2 のとおりです。

リスト 2. 会合の順不同リストの HTML
<!DOCTYPE html>
<html>
<head>
    <title>Meetups Component</title>
    
</head>
<body>

   <ul>
     <li class="mobile">Sencha Touch. 
        <span class='date'>Jan 23</span></li>

     <li class="html5-in-general">HTML5 & Semantic Markup. 
        <span class='date'>Jun 18</span></li>

     <li class="html5-in-general">HTML5 & Windows 8. 
        <span class='date'>Jul 23</span></li>

     <li class="javascript">HTML5 JavaScript on Crack. 
        <span class='date'>Aug 20</span></li>

     <li class="javascript">CoffeeScript for Recovering JavaScript Programmers. 
        <span class='date'>Sept 17</span></li>

     <li class="html5-in-general">ClojureScript and CouchDB. 
        <span class='date'>May 21</span></li>

     <li class="html5-in-general">Polyfills for the Pragmatist. 
        <span class='date'>Apr 23</span></li>

     <li class="design">CSS3 for Programmers. 
        <span class='date'>Feb 20</span></li>

     <li class="lightning">Quick-start web apps; 
     Graphics; Data visualization; Adaptive patterns; JSON. 
        <span class='date'>Oct 22</span></li>

     <li class="lightning">Web workers; CSS3; HTTP; Audio, video, & canvas; 
     Charting; JS evolution. 
        <span class='date'>March 19</span></li>
   </ul>

</body>
</html>

リスト 2 の HTML は平凡なもので、単にリスト項目を並べた順不同リストを作成しているだけです。図 7 に示すのは同じリスト項目ですが、1 つの順不同リストにする代わりに、リスト項目をカスタム・コンポーネント内部に配置し、カスタム・コンポーネントによってリスト項目が操作されるようにします。

図 7. カスタム会合リスト・コンポーネント
カスタム会合リスト・コンポーネントによって生成された会合リストのスクリーン・キャプチャー

リスト 3 に、図 7 に示したアプリケーションのマークアップを記載します。

リスト 3. 順不同リストに代わる meetup-talks コンポーネント
<!DOCTYPE html>
<html>
<head>
    <title>Meetups Component</title>
    <script src="../polymer/polymer.js"></script>
    <link rel="components" href="meetup-component.html">

</head>
<body>

   <meetup-talks>
     <li class="mobile">Sencha Touch. 
        <span class='date'>Jan 23</span></li>

     <li class="html5-in-general">HTML5 & Semantic Markup. 
        <span class='date'>Jun 18</span></li>

     <li class="html5-in-general">HTML5 & Windows 8. 
        <span class='date'>Jul 23</span></li>

     <li class="javascript">HTML5 JavaScript on Crack. 
        <span class='date'>Aug 20</span></li>

     <li class="javascript">CoffeeScript for Recovering JavaScript Programmers. 
        <span class='date'>Sept 17</span></li>

     <li class="html5-in-general">ClojureScript and CouchDB. 
        <span class='date'>May 21</span></li>

     <li class="html5-in-general">Polyfills for the Pragmatist. 
        <span class='date'>Apr 23</span></li>

     <li class="design">CSS3 for Programmers. 
        <span class='date'>Feb 20</span></li>

     <li class="lightning">Quick-start web apps; 
     Graphics; Data visualization; Adaptive patterns; JSON. 
        <span class='date'>Oct 22</span></li>

     <li class="lightning">Web workers; CSS3; HTTP; Audio, video, & canvas; 
     Charting; JS evolution. 
        <span class='date'>March 19</span></li>
   </meetup-talks>

</body>
</html>

リスト 3 のマークアップはリスト 2 のマークアップとほぼ同じですが、以下の 3 つの点が異なります。

  • リスト 3 のマークアップは、polymer.js という名前のファイルを読み込みます。このファイルは、HTML 5 Web Components の API (Shadow DOM、テンプレートなど) の実装を提供する Polymer オープンソース・プロジェクトからのものです。この後の「Polymer」セクションで詳しく説明しますが、Polymer プロジェクトは、Chrome 開発チームによって実装されています。
  • リスト 3 では link 要素を使用して別の HTML5 ファイルをインポートしています。このように link 要素を使用する方法は、「HTML インポート」として知られています。この記事を執筆している時点では、HTML インポートを実装しているブラウザーはありません。この例の場合、HTML インポートの機能は Polymer プロジェクトによって提供されます。
  • リスト 2 の順不同リスト要素は、リスト 3meetup-talks カスタム要素に置き換えられています。

meetup-talks カスタム要素は、リスト 4 に記載する meetup-component.html 内に実装されます。

リスト 4. meetup-list 要素
<element name="meetup-list">
  <template>
    <style>
      /* styles that follow only apply to the ShadowDOM of the <meetup-list> element */

      #meetups {
        display: block;
        padding: 15px;
        padding-top: 0px;
        background: lightgray;
        border: thin solid cornflowerblue;
      }

      .title {
        color: blue;
        font-size: 1.5em;
      }
    </style>

    <div id='meetups'>
      <p class='title'>HTML5 in General</p>
      <content select='.html5-in-general'></content>
    
      <p class='title'>JavaScript</p>
      <content select='.javascript'></content>
    
      <p class='title'>Design</p>
      <content select='.design'></content>
    
      <p class='title'>Lightning</p>
      <content select='.lightning'></content>
    </div>
  </template>

  <script>
    Polymer.register(this);
  </script>
</element>

面白くなってくるのは、リスト 4 からです。このリストではまず、HTML 仕様の要件に従ってカスタム要素の名前にハイフンを含めることに注意して、meetup-list という名前の新しい要素を宣言しています。この meetup-list 要素は、リストの最後にある 1 行の JavaScript によって Polymer グローバル・オブジェクトに登録されます。この 1 行の JavaScript が、カスタム要素の宣言と登録を処理して、HTML ページでカスタム要素を使用できるようにします。

element 要素の中では、template を宣言しています。テンプレートが Shadow DOM を定義する方法は、プログラマチックな方法ではなく宣言型による方法です。そこで、リスト 4 では Shadow DOM テンプレートを作成しています。HTML ページ内でこの meetup-list 要素を使用すると、ブラウザーは必ずこの Shadow DOM テンプレートを使用して、当該要素のための新しい Shadow DOM インスタンスをスタンプアウト (作成) します。

テンプレート内では、「そのテンプレートに含まれる要素のみに適用される」 2 つの CSS スタイルを定義しています。それとは反対に、周囲の文書内で定義された CSS スタイルは、「テンプレートに含まれる要素には影響を与えません」(テンプレートは Shadow DOM を表すため)。例えば、リスト 3 の HTML に段落要素のスタイルを追加したとしても、そのスタイルはテンプレート内の段落には影響を与えません。

このテンプレートは CSS スタイルを定義するだけでなく、それに続く Shadow DOM インスタンス内でも要素を定義しています。content 要素は、CSS セレクターを使用して meetup-list の「元のコンテンツ」からリスト項目を選択します。したがって、テンプレートでは Shadow DOM インスタンスを宣言型で指定する他に、「要素の元のコンテンツを選択的にテンプレート自身に挿入する」こともできます。<content></content> を使用することで、要素の元のコンテンツ全体をテンプレートに挿入できることにも注意してください。


Web Components の実装

この記事で説明する Web Components API はいずれも比較的新しいものであり、ブラウザーのベンダーによってサポートの度合いは異なります。現在のところ、コードを作成してそれをブラウザーにロードするだけでは、コンポーネントを効果的に実装することはできません。代わりに、HTML5 の分野で「ポリフィル」または「シム」として知られるものが必要になります。ポリフィルとは、新しい機能が使用可能な場合にはそれを使用し、使用可能でない場合にはサポートされる代替手段にフォールバックできるようにするためのソリューションです。

現時点で Web Components のポリフィルを提供しているプロジェクトには、Polymer と Mozilla X-Tag の 2 つがあります。Polymer は Chrome でのみ動作し、Web サーバーがなければ HTML インポートを使用できないため、本番で使用するには適していません。X-Tag は最近のほぼすべてのブラウザーで動作し、Web サーバーも不要です。ただし、X-Tag が実装するのはカスタム要素の API だけに限られ、テンプレートと Shadow DOM は除外されています。

ここからは、Polymer と X-Tag をそれぞれ使用して、連載の以前の記事で実装した特定の目的のためのスライダーを標準コンポーネントでラップする方法を説明します。


Polymer

その名のとおり、特定の目的のためのコンポーネントはコンポーネントの標準には従わないため、これらのコンポーネントを実装または使用するための標準的な方法はありません。けれども、特定の目的のためのコンポーネントを標準コンポーネントの中にラップすれば、これらのコンポーネントを簡単に使用できるようになります。まずは Polymer を使用して、特定の目的のためのスライダーを標準コンポーネントでラップします。図 8 に、Polymer バージョンのスライダーを示します。

図 8. Polymer によるスライダー
Polymer で実装されたスライダーのスクリーン・キャプチャー

リスト 5 に、Polymer バージョンのスライダーを使用する方法を示します。

リスト 5. Polymer のスライダー・タグを使用する
<!DOCTYPE html>
<html>
   <head>
      <title>Slider with Polymer</title>
    <script src="../polymer/polymer.js"></script>
    <script src="lib/slider.js"></script>
    <link rel="import" href="./slider.html" />
   </head>
   
   <body>
     <x-slider id='slider'
                fillColor='goldenrod'
                strokeColor='red'>
     </x-slider>
   </body>
</html>

連載の最初の記事に記載したリスト 2 (特定の目的のためのスライダーを単独で使用する方法を示したリスト) リスト 5 を比較してみると、リスト 5 のほうが大幅に簡潔になっていることがわかります。Polymer の x-slider 要素を使用すれば、たった 1 行の HTML でスライダーを作成することができます。

Polymer の x-slider 要素は、リスト 6 で実装されます。

リスト 6. Polymer のスライダー・コンポーネント
<element name="x-slider" attributes="strokeColor fillColor">
  <template>
    <style>
       x-slider {
          display: block;
       }

       .x-slider-button {
          float: left;
          margin-left: 2px;
          margin-top: 5px;
          margin-right: 5px;
          vertical-align: center;
          border-radius: 4px;
          border: 1px solid rgb(100, 100, 180);
          background: rgba(255, 255, 0, 0.2);
          box-shadow: 1px 1px 4px rgba(0,0,0,0.5);
          cursor: pointer;
          width: 25px;
          height: 20px;
       }

       .x-slider-button:hover {
          background: rgba(255, 255, 0, 0.4);
       }

       #xSliderDiv {
          position: relative;
          float: right;
          width: 80%;
          height: 65%;
       } 
    </style>

    <div id='x-slider-buttons-div' 
         style='width: {{width}}px; height: {{height}}px;'>

       <button class='x-slider-button' id='xSliderMinusButton'>-</button>
       <button class='x-slider-button' id='xSliderPlusButton'/>+</button>
       <div id='xSliderDiv'></div>

    </div>
  </template>

  <script>
    Polymer.register(this, {
        width: 350,
        height: 50,
        strokeColor: 'blue',
        fillColor: 'cornflowerblue',

        ready: function () { 
          var self = this,
              slider = new COREHTML5.Slider(this.strokeColor, 
                                            this.fillColor, 0);

          setTimeout( function () { // This timeout is a hack
            slider.appendTo(self.$.xSliderDiv);
            slider.draw();
          }, 200);

          this.$.xSliderMinusButton.onclick = function () {
            if (slider.knobPercent >= 0.1) {
              slider.knobPercent -= 0.1;
              slider.erase();
              slider.draw();
            }
          };

          this.$.xSliderPlusButton.onclick = function () {
            if (slider.knobPercent <= 0.9) {
              slider.knobPercent += 0.1;
              slider.erase();
              slider.draw();
            }
          };
        }
    });

  </script>
</element>

リスト 6リスト 4 と同様で、どちらのリストでも新しい要素を定義して、その要素を Polymer グローバル・オブジェクトに登録しています。また、どちらもテンプレートを定義し、そこに CSS スタイルとマークアップを含めています。リスト 6リスト 4 の違いは、リスト 6 では Polymer データ・バインディングと ready イベント・ハンドラーを使用しているという点です。

二重の「口ひげ」の表記 ({{width}}) が示しているのは、要素のプロパティーです。二重の口ひげの表記は、要素のマークアップ内で使用することができます。リスト 6 では、要素を取り囲む DIV の幅と高さを、ページ作成者が要素のプロパティーを使用して設定する幅と高さに設定しています。

プロパティーを宣言するのは、Polymer.register() メソッドに渡すオブジェクト内です。例えば、リスト 6 で宣言している x-slider 要素のプロパティーには、widthheightstrokeColorfillColor の 4 つがあります。element 要素の attributes 属性を使用してプロパティーを指定すると (「プロパティーのパブリッシング」として知られています)、ページ作成者はこれらのプロパティーを自分たちの要素で使用できるようになります。リスト 5x-slider 要素はその一例です。

Polymer のドキュメントによると、「コンポーネントは自身の初期化を完了した時点で」、ready() メソッドを呼び出します (コンポーネントに、このメソッドが存在する場合)。このドキュメントには、コンポーネントが自身の初期化をいつ行うかについて、これ以上詳しい説明はありませんが、明らかにそれは、ブラウザーがコンポーネントを描画する準備が整う前です。コンポーネントを描画する準備が整う前にコンポーネントの初期化が行われること、そしてその時点でライフサイクルに存在するメソッドは ready() だけであることから、ready() メソッドが呼び出されてから 200ms 後にスライダーを描画するように、setTimeout() メソッドを追加する必要がありました。このような不備は、そのうち必ず取り除かれるはずです (Web Components 仕様では、ブラウザーが要素を DOM に挿入した後に呼び出す inserted コールバックも定義していますが、この記事が公開される時点では、Polymer はこのコールバックをサポートしていません)。

Polymer では、カスタム要素に $ 属性を指定し、この属性で要素の属性のマップを参照することもできます。私はそのマップを使用して、特定の目的のためのスライダーを最終的に含める DIV にアクセスしています。


X-Tag

図 9 に、X-Tag バージョンのスライダーを示します。

図 9. X-Tag のスライダー・コンポーネント
X-Tag で実装されたスライダーのスクリーン・キャプチャー

スライダーの Polymer 実装と X-Tag 実装は、見た目には区別することができません。私はこれらの実装を異なる色を使用して実装しましたが、それは単に、異なるフレームワークで実装されていることを示すためです。リスト 7 に、図 9 のアプリケーションで X-Tag バージョンのスライダー (同じく x-slider として実装されています) を使用する方法を示します。

リスト 7. X-Tag のスライダー・コンポーネントを使用する
<!DOCTYPE html>
<html>
   <head>

      <title>Slider with x-tag</title>
      <link rel="stylesheet" type="text/css" href="x-slider.css" />
   </head>
   
   <body>
      <x-slider id='slider'
                slider-fill-color='rgba(50, 105, 200, 0.8)'
                slider-stroke-color='navy'>
      </x-slider>
   </body>

   <script type="text/javascript" src="lib/x-tag.js"></script>
   <script type="text/javascript" src="lib/slider.js"></script>
   <script type="text/javascript" src="x-slider.js"></script>
</html>

リスト 7リスト 5 と同様で、どちらのリストでも単純な x-slider 要素を使用してページにスライダーを取り込みます。異なる点は、Polymer バージョンでは HTML インポートを使用して、スライダーを定義する HTML フラグメントを読み込んでいる一方、X-Tag バージョンでは HTML インポートをサポートしていないことから、代わりに JavaScript を読み込んでいるところです。

リスト 8 に、コンポーネントを実装する JavaScript を記載します。

リスト 8. スライダーの JavaScript
function getFirstAncestor(name, startingElement) {
   var  element = startingElement
      , localName = element.localName;

   while (localName !== name) {
      element = element.parentNode;
      localName = element.localName;
   }

   return element;
};
   
function getSlider(element) {
   return getFirstAncestor('x-slider', element).slider;
};
   
xtag.register('x-slider', { 
   onCreate: function () { 
      var content =
             "<div id='x-slider-buttons-div'>"                                          +
                "<button class='x-slider-button' id='x-slider-minus-button'>-</button>" +
                "<button class='x-slider-button' id='x-slider-plus-button'>+</button>"  +
             "</div>"                                                                   +
             ""                                                                         +
             "<div id='x-slider-slider-div'></div>"                                     +
             ""                                                                         +
             "<div id='x-slider-readout-div'></div>";

          stroke = this.getAttribute('slider-stroke-color'),
          fill   = this.getAttribute('slider-fill-color');
 
         // 'this' is the x-slider HTML element

         this.max = 100;

         this.innerHTML = content;
         this.slider = new COREHTML5.Slider(stroke, fill, 0);

         this.slider.appendTo('x-slider-slider-div');
         this.slider.draw();
      },

      events: {
         'click:touch:delegate(#x-slider-plus-button)': function(event, slider) {
            var slider = getSlider(this)  // 'this' is the button
               , value = getFirstAncestor('x-slider', this).getValue();
               
            if (slider.knobPercent <= 0.9) {
               slider.knobPercent += 0.1;
            }
            slider.erase();
            slider.draw();

            console.log(value);
         },

         'click:touch:delegate(#x-slider-minus-button)': function(event, slider) {
            var slider = getSlider(this)  // 'this' is the button
               , value = getFirstAncestor('x-slider', this).getValue();

            if (slider.knobPercent >= 0.1) {
               slider.knobPercent -= 0.1;
            }
            slider.erase();
            slider.draw();

            console.log(value);
         },
      },

      methods: {
         getValue: function () {
            // 'this' is the x-slider HTML element
            return this.slider.knobPercent * this.max; 
         }
      }
});

リスト 8 の X-Tag によるコンポーネントの定義は、リスト 6 の Polymer による定義とはかなり異なります。X-Tag は要素を JavaScript のみで定義します。そのためには、ストリングの連結を使用すること、そして要素の内部 HTML を設定して、カスタム要素のコンテンツを設定することが必要になります。これは、Polymer とはまったく対照的です。Polymer では、カスタム要素のコンテンツを極めて読みやすくて保守もしやすい HTML で定義します。

X-Tag には、カスタム要素に含まれる要素に簡単にアクセスできるようにする、Polymer の $ のような属性がありません。そのため、特定の目的のためのスライダーを収容する要素にアクセスするために、getFirstAncestor() 関数を実装しなければなりませんでした。

X-Tag は、スライダーのノブを制御するボタンのクリックを処理するためのイベントの実装をサポートしていますが、この演習には、そのサポートはやり過ぎであるように思いました。

完全を期して、X-Tag バージョンのスライダーに対応する CSS をリスト 9 に記載します。

リスト 9. スライダーの CSS
x-slider {
   display: block;
}

.x-slider-button {
   float: left;
   margin-left: 2px;
   margin-top: 15px;
   margin-right: 5px;
   vertical-align: center;
   border-radius: 4px;
   border: 1px solid rgb(100, 100, 180);
   background: rgba(255, 255, 0, 0.2);
   box-shadow: 1px 1px 4px rgba(0,0,0,0.5);
   cursor: pointer;
   width: 25px;
   height: 20px;
}

.x-slider-button:hover {
   background: rgba(255, 255, 0, 0.4);
}

#x-slider-buttons-div {
   width: 25%;
   height: 100%;
} 

#x-slider-slider-div {
   position: relative;

   float: right;
   margin-top: -40px;
   width: 80%;
   height: 65%;
}

まとめ

現在、各 JavaScript フレームワークが実装している特定の目的のためのコンポーネントは、概ね標準ではなく独自仕様の API に従っています。そのため、新しいフレームワークに移行する際には、そのフレームワークについてかなり学習しなければならないのが常となっています。さらに、大抵の場合、特定の目的のためのコンポーネントはカプセル化されていないため、その機能がアプリケーションの他のコンポーネントによる悪影響を受ける恐れがあります。

この連載では、HTML5 API を使用して標準コンポーネントと特定の目的のためのコンポーネントの両方を実装する方法を説明しました。コンポーネントを実装するための HTML5 仕様は比較的新しく、この仕様のサポート状況はブラウザーのベンダーによって異なります。標準コンポーネントの実装は今すぐにでも始められますが、そのために開発者はブラウザー以上のことをしなければなりません。今回の記事では、標準コンポーネントの実装を可能にする 2 つのフレームワーク、Polymer と X-Tag について簡単に説明しました。

Web Components 仕様の成熟度が増し、その機能をブラウザーのベンダーが実装するようになっていることから、近い内に標準コンポーネントを作成するための有望なプラットフォームを使用できるようになることでしょう。その暁には、HTML 5 アプリケーションを実装するのが今よりもさらに面白くなるはずです。


ダウンロード

内容ファイル名サイズ
Sample codewa-html5-components-3-code.zip1.68MB

参考文献

学ぶために

製品や技術を入手するために

  • Google Polymer: Polymer のコードを入手してください。
  • IBM 製品の評価版: DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を体験するには、評価版をダウンロードするか、IBM SOA Sandbox のオンライン試用版を試してみてください。

議論するために

  • developerWorks コミュニティー: ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、Wiki を調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=942705
ArticleTitle=HTML5 コンポーネント: 標準コンポーネントの実装
publish-date=08292013