JavaScript

JavaScript を使って作成する Windows ストア アプリでのコントロールの構築と使用

Chris Sells
Brandon Satrom

コード サンプルのダウンロード

以前の記事「JavaScript を使って作成する Windows ストア アプリでのデータ バインド」(英語) で、JavaScript 用 Windows ライブラリ (WinJS) と WinJS によるデータ バインドのサポートについて詳しく説明しました。データ バインドは、多くの場合、コントロールのコンテキスト内で使用されるため、WinJS によるデータ バインドのサポートと ListView コントロールのフィードの調査に多くの時間を費やしました (msdn.microsoft.com/magazine/jj651576、英語)。今回は、Windows ストア アプリを作成する JavaScript プログラマが利用できるコントロールの概要を簡単に説明します。ここで説明するコントロールがニーズに合わない場合に備えて、独自にコントロールを作成する方法も取り上げます。

Windows ストア アプリでコントロールを構築するために JavaScript を使用しているときは、次のようにいくつかのファミリのコントロールにアクセスできます。

  • HTML5 要素: HTML5 の要素は、再利用可能な UI やビヘイビアのブロックという点でコントロールと言えます。<progress /> や <a /> がその例です。
  • WinRT コントロール: JavaScript に提供する Windows ランタイム (WinRT) クラスとして公開されるコントロールです。Windows.UI.Popups.PopupMenu などがその例です。
  • WinJS コントロール: JavaScript クラスとして実装されるコントロールです。WinJS.UI.ListView などがその例です。
  • CSS スタイル: CSS はコンテンツ アイテムをコントロール コンテナーのようにレイアウトできるスタイルをいくつか提供します。column-count: 4 がその例です。

今回は、最初の 3 つのカテゴリのコントロールに注目します。

HTML5 要素

JavaScript を使って作成する Windows ストア アプリは Web テクノロジに基づくため、HTML5 要素はすべて適切に機能します (図 1 参照)。

HTML5 Controls Available to Your Windows Store Apps Built with JavaScript
図 1 JavaScript を使って作成する Windows ストア アプリで利用できる HTML5 コントロール

ここでは HTML5 要素の詳細については取り扱いませんので、詳しくは手近に利用できる HTML5 のドキュメントを参考にしてください。図 1 を作成するために使用したサンプルは、付属のソース コードのダウンロードで提供しています。

WinRT コントロール

Windows ランタイムはさまざまな領域でさまざまな機能を提供しますが、コントロールについて提供するのは次の 2 つの機能のみです。

  • メッセージ ダイアログ: タイトル (省略可能) を付けたメッセージ
  • ポップアップ メニュー: メニュー項目が 6 個以下に制限されているメニュー

メッセージ ダイアログは MessageDialog 関数を使用して呼び出します。

var popups = Windows.UI.Popups;
var mb = new popups.MessageDialog("and welcome to my message box!", "Hello!");
mb.showAsync();

JavaScript を使用して作成する Windows ストア アプリのすべての非同期操作には、自由に使用できる showAsync メソッドがあるの同様に、MessageDialog クラスにも promise を返す showAsync メソッドがあります。ただし、多くの場合、メッセージ ダイアログが閉じられるときに特別な処理は必要ないため、ここでは promise を無視しています。図 2 に結果を示します (MessageDialog の旧称 "MessageBox" を使用しています)。

The WinRT Message Dialog
図 2 WinRT メッセージ ダイアログ

PopupMenu クラスも同じように使用します。

var popups = Windows.UI.Popups;
var menu = new popups.PopupMenu();
menu.commands.push(new popups.UICommand("one", null, 1));
menu.commands.push(new popups.UICommand("two", null, 2));
menu.showAsync({ x: 120, y: 360 }).done(function (e) {
  // Do something with e.label and/or e.id
  ...
});

この例では、PopupMenu オブジェクトを作成後、2 つの UIComand オブジェクトを提供して、それぞれラベル、省略可能なコールバック、および ID の各パラメーターを渡しています。"done" 完了メソッドでイベント パラメーターを受け取るため、この例ではコマンド コールバックは使用しません。ポップ アップ メニューは予想どおりに表示されます (図 3 参照)。

The WinRT Pop-up Menu
図 3 WinRT ポップアップ メニュー

このコラムの執筆時点では、ポップ アップ メニューのメニュー項目数は 6 個に制限されています。

WinJS コントロール

HTML5 コントロールは機能が豊富でバラエティに富んでいますが、World Wide Web コンソーシアムが新しい要素タグの追加を決めたり、ブラウザー ベンダーが追加機能を実装しない限り、このコントロールのセットは拡張されません。同様に、WinRT コントロールのセットも拡張されません (ただし、UI 以外の Windows ランタイム コンポーネントを作成することは可能です)。特に Windows ストア アプリで具体的に作成しているコントロール セットを拡張可能にすることを考えている場合は、WinJS が提供するコントロールのセットが最適です。

WinJS コントロールは JavaScript で実装されるコントロールで、コンストラクター関数で特定のシグネチャを提供します。

function MyControl(element,
    options) {...}

element 引数は、HTML ドキュメント オブジェクト モデル (DOM) 要素で、多くの場合、このコントロールのコンテンツのホストとして機能する div を意味します。options 引数は JavaScript オブジェクトで、ListView の itemDataSource プロパティのような省略可能な構成引数を提供するために使用します。

WinJS コントロールの動作を確認するため、DatePicker コントロールのホストとして機能する次の div を考えてみましょう。

<div id="datePickerDiv"></div>

これを適切に使用して、次のような DatePicker を簡単に作成できます。

var datePicker = new WinJS.UI.DatePicker(datePickerDiv);

出力はまったく新しい DatePicker コントロールになります (図 4 参照)。

Output of the DatePicker Control
図 4 DatePicker コントロールの出力

コントロールを構成する場合は、一連のオプションを渡すことができます。

var datePicker = new WinJS.UI.DatePicker(datePickerDiv, { current: "6/2/1969" });

この DatePicker の場合、current オプションを使用して現在表示日付を設定できます (図 5 参照)。

Setting the Currently Displayed Date
図 5 現在表示日付の設定

HTML5 要素にコントロールを関連付けたら、winControl プロパティを使用してこのコントロールを取得できます。

var datePicker = datePickerDiv.winControl; // Magical well-known property name
datePicker.current = "5/5/1995"; // Now we're talking to the control

さらに、コントロールを取得したら、element プロパティを使って、関連付けられている HTML5 要素に返すことができます。

var datePickerDiv = datePicker.element;
datePickerDiv.style.display = "none";
// Now we're talking to the HTML element

各コントロールはプログラムで作成することができることに加えて、data-win-control 属性や data-win-options 属性を使って宣言によって作成することも可能です。

<div id="datePicker2"
  data-win-control="WinJS.UI.DatePicker" data-win-options=
  "{current: '6/2/1969'}" ></div>

data-win-control 属性は、呼び出すコンストラクター関数の名前です。data-win-bind 属性の解析に WinJS.Binding.processAll の呼び出しを必要とするデータ バインド (以前のコラムを参照) と同様に、data-win-control プロパティでは解析してコントロールを作成するために、WinJS.UI.processAll を呼び出す必要があります。生成されるプロジェクト テンプレートのコードすべてに WinJS.UI.processAll の呼び出しがあるのはこのためです。

data-win-options の文字列は、あまり強力ではない JavaScript のオブジェクト初期化構文として解析されます。たとえば、DatePicker コントロールの場合は、current オプションを設定する代わりに、Date オブジェクトを作成して、直接この文字列を渡せることがわかります。これは、オプションのパーサーは静的データのみに作用し、"new" キーワードを理解しないためです。ただし、DatePicker などの WinJS コントロールは宣言によって作成されることが想定されるため、オプションのパーサーの制限事項に特別な余地が残されます。つまり、この例の DatePicker の場合、文字列を受け取り、この文字列を Date オブジェクトとして解析します。

各コントロールにはそれぞれ異なるオプションのセットがあるため、コントロールとオプションの対応を示します。図 6 は組み込みの WinJS コントロールのリストを示しています。

図 6 WinJS コントロール

名前 説明 クラス
App Bar ツールバーにアプリレベルのコマンドを表示する WinJS.UI.AppBar
Date Picker 日付を選択するための UI を表示する WinJS.UI.DatePicker
Flip View 一度に 1 項目ずつコンテンツのセットを反転する WinJS.UI.FlipView
Flyout 任意のコンテンツを使ってオーバーレイを表示する WinJS.UI.Flyout
List View 項目のコレクションをグループ化した状態またはグループ化を解除した状態でリスト表示またはグリッド表示する WinJS.UI.ListView
Rating 映画などを評価するための UI を表示する WinJS.UI.Rating
Semantic Zoom あるリストビューから別のリストビューにズームする UI を提供する (例、グループ化されたリストビューをグループのリストにズーム アウトする) WinJS.UI.SemanticZoom
Settings Flyout アプリの設定を構成するための UI を提供する WinJS.UI.SettingsFlyout
Time Picker 日付を選択するための UI を表示する WinJS.UI.TimePicker
Toggle Switch 2 つの選択肢を切り替える UI を表示する WinJS.UI.ToggleSwitch
Tooltip (Rich) 任意の HTML コンテンツを持つヒントを表示する WinJS.UI.Tooltip
View Box 固定サイズの領域を空き領域全体に論理的に拡大する WinJS.UI.ViewBox

図 7 に WinJS controls の動作を示します。

The WinJS Controls in Action
図 7 WinJS コントロールの動作

Windows ストア アプリで HTML5 コントロール、WinRT コントロール、および WinJS コントロールを自由に混在させたり、組み合わせたりしてください。

ただし、ここで示した HTML5、Windows ランタイム、または WinJS のコントロールに希望するコントロールが見つからない場合は、独自に作成することができます。

カスタム コントロール

既に説明したように、WinJS コントロールは、コンストラクターが次の形式で提供される関数にすぎません。

function MyControl(element, options) {...}

このようなコントロールの作成では、1 つ目の引数に渡される親要素の HTML を作成して関数を実装し、2 つ目の引数に渡される options オブジェクトを使用するだけです。たとえば、小さな時計コントロールを作成するとします (図 8 参照)。

A Custom Clock Control
図 8 カスタム時計コントロール

時計コントロールを配置する場合は次のように div 要素を使用するものとします。

<div id="clockControl1"></div>

組み込みの WinJS コントロールと同様、このカスタム コントロールのインスタンスを次のように作成できるようにします。

var clock = new Samples.UI.ClockControl(clockControl1, { color: 'red' });
clock.color = 'red'; // Can set options as part of construction or later

カスタム コントロールの名前は、Samples.UI 名前空間の、ClockControl としました。以前と同様に、コントロールの作成では、コンテナーとなる element (clockControl1) と省略可能な options の名前と値のペアのセットを渡すだけです。コントロールの有効期間内に後からコントロールの options を変更する場合は、個々のプロパティ値を設定して変更できるようにします。

また、次のようにカスタム コントロールを宣言によって作成することも可能にします。

<script src="/js/clockControl.js"></script>
  ...
  <div id="clockControl2"
    style="width: 200px; height: 200px;"    
    data-win-control="Samples.UI.ClockControl"
    data-win-options="{color: 'red'}">  </div>

実装の一環として、winControl と element プロパティが設定されているか、プライベート メンバーが適切に指定されているか、およびイベントが適切に処理できるかを確認するものとします。ここからは、この ClockControl の実装を詳しく説明しながら、WinJS がこれらの機能の実装にどのように役立つかを見ていきます。

コントロール クラス: まず、ClockControl が適切な名前空間に所属させる必要があります。近代言語のほとんどには、型、関数、および値を個別の名前がついた領域に分けて競合を回避する手段として、名前空間の考え方があります。たとえば、マイクロソフトが WinJS 2.0 で ClockControl 型を提供することになれば、おそらく WinJS.UI 名前空間で提供します。そのため、Samples.UI と競合することはありません。JavaScript では、名前空間は、コンストラクター、関数、および値とは別のオブジェクトであり、次のように設定します。

// clockControl.js
(function () {
  // The hard way
  window.Samples = window.Samples || {};
  window.Samples.UI = window.Samples.UI || {};
  window.Samples.UI.ClockControl = 
    function(element, options) { ... };
})();

これは適切に機能します。しかし、よく行われているのは名前空間 (およびネストされた名前空間) を定義する方法で、多くの JavaScript ライブラリと同様、WinJS では次のようなショートカットが用意されています。

// clockControl.js
(function () {
  // The easy way
  WinJS.Namespace.define("Samples.UI", {
    ClockControl: function (element, options) { ... };
  };
})();

WinJS.Namespace 名前空間の define 関数を使うと、新しい名前空間を作成し、ドット付きの名前を適切に解析できます。2 つ目の引数は、この名前空間から公開するコンストラクター、関数、値などを定義するオブジェクトです。この場合は、ClockControl のコンストラクターです。

コントロールのプロパティとメソッド: 今回の ClockControl 型では、color プロパティなど、メソッドやプロパティをいくつか公開します。これらのメソッドやプロパティは、インスタンスでも静的でも、パブリックでも (JavaScript でオブジェクトのメンバーを取得できる "プライベート" でさえあれば) プライベートでもかまいません。これらの概念はすべて、コンストラクターの prototype プロパティ、および JavaScript の新しい Object.defineProperties メソッドを適切に使うことでサポートされます。WinJS.Class 名前空間の define メソッドを通じて、WinJS はここでもショートカットを用意しています。

WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(
    function (element, options) {...}, // ctor
  { // Properties and methods
    color: "black",
    width: { get: function () { ... } },
    height: { get: function () { ... } },
    radius: { get: function () { ... } },
    _tick: function () { ... },
    _drawFace: function () { ... },
    _drawHand: function (radians, thickness, length) { ... },
  })
});

WinJS.Class.define メソッドは、コンストラクターとして動作する関数だけでなく、一連のプロパティとメソッドも受け取ります。define メソッドは用意される get 関数と set 関数からプロパティの作成方法を把握します。さらに、_tick のような、プレフィックスにアンダースコアの付いたプロパティやメソッドが "プライベート" を意味することも認識します。JavaScript は、従来の意味でのプライベート メソッドを実際にサポートしているわけではありません。つまり、依然として _tick メソッドを呼び出すことができます。ただし、少なくともパブリックで使用するものでないことを伝える便利な方法なので、Visual Studio 2012 の IntelliSense や、JavaScript の for-in ループでは表示されません。

コンストラクターでは、WinJS コントロールに必要なプロパティを設定します (図 9 参照)。

図 9 コンストラクターで WinJS コントロールに必要なプロパティを設定する

WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(function (element, options) {
    // Set up well-known properties
    element.winControl = this;
    this.element = element;
    // Parse the options; that is, the color option
    WinJS.UI.setOptions(this, options);
    // Create the drawing surface
    var canvas = document.createElement("canvas");
    element.appendChild(canvas);
    this._ctx = canvas.getContext("2d");
    // Draw the clock now and every second
    setTimeout(this._tick.bind(this), 0);
    setInterval(this._tick.bind(this), 1000);
  },
  ...
});

コンストラクターでは、まず既知の winControl と element プロパティを設定し、ホストしている HTML5 要素と JavaScript コントロールを切り替えられるようにします。

次に、options を処理します。前述のように、options は、名前と値のペアのセット、または HTML5 の data-win-options 属性を使用して文字列で指定できます。WinJS では、options の文字列を JavaScript オブジェクトとして解析するため、開発者は名前と値のペアを扱うだけで済みます。必要に応じて、個別のプロパティを取り出すことができます。たとえば、今回の例では color プロパティです。ただし、options のリストが大きい場合は、WinJS.UI 名前空間の setOptions メソッドで options オブジェクトのプロパティすべてを調べ、それらをコントロールのプロパティに設定します。たとえば、以下の 2 つのコードのブロックは同じ働きをします。

// Setting each property one at a time
myControl.one = "one";
myControl.two = 2;
// Setting all properties at once
WinJS.UI.setOptions(myControl, {
  one: "one",
  two: 2,
});

コントロールの options を設定したら、作業の完了に必要な HTML5 親要素の子要素を作成します。ClockControl の場合、HTML5 の canvas 要素とタイマーを使用しています。このコントロールの実装は、従来のプレーンな HTML と JavaScript のみなので、ここでは説明しません (付属のコード ダウンロードで利用できます)。

コントロールのイベント: メソッドとプロパティに加え、コントロールでは多くの場合、イベントを公開します。イベントは、ユーザーがコントロールをクリックした、コントロールがプログラムである種の動作をトリガーする状態になったなどの、コントロールに関係ある出来事が起こったことを伝える通知です。HTML DOM で設定した例に応じて、コントロールが公開するイベントと、対応する onmyevent プロパティを開発者がサブスクライブできるように addEventListener や removeEventListener などのメソッドが必要になります。

たとえば、例の ClockControl から 5 秒ごとにイベントを発生させる場合、次のようにプログラムしてサブスクライブすると考えられます。

// Do something every 5 seconds
window.clockControl_fiveseconds = function (e) {
  ...
};
var clock = new Samples.UI.ClockControl(...);
// This style works
clock.onfiveseconds = clockControl_fiveseconds;
// This style works, too
clock.addEventListener("fiveseconds", clockControl_fiveseconds);
Declaratively, we’d like to be able to attach to custom events, too:
<!-- this style works, three -->
<div data-win-control="Samples.UI.ClockControl"
  data-win-options="{color: 'white',
    onfiveseconds: clockControl_fiveseconds}" ...>
</div>

これら 3 つのスタイルすべてを実現するためには、イベントのサブスクリプション (およびイベントが発生したときのディスパッチ) を管理するメソッドと各イベントのプロパティの 2 つが必要です。この両方が WinJS.Class 名前空間で提供されます。

// clockControl.js
...
WinJS.Namespace.define("Samples.UI", {
  ClockControl: WinJS.Class.define(...);
});
// Add event support to ClockControl
WinJS.Class.mix(Samples.UI.ClockControl, 
  WinJS.UI.DOMEventMixin);
WinJS.Class.mix(Samples.UI.ClockControl,  
  WinJS.Utilities.createEventProperties("fiveseconds"));

WinJS.Class の mix メソッドで、既存のオブジェクトが提供するプロパティとメソッドを追加できます。今回は、WinJS.UI の DOMEventMixin が 3 つのメソッドを提供しています。

// base.js
var DOMEventMixin = {
  addEventListener: function (type, listener, useCapture) {...},
  dispatchEvent: function (type, eventProperties) {...},
  removeEventListener: function (type, listener, useCapture) {...},
};

DOMEventMixin のメソッドを追加したら、WinJS.Utilities の createEventProperties メソッドで作成したオブジェクトで mix メソッドを使用して各カスタム イベントのプロパティを作成できます。このメソッドでは、"on" プレフィックスを追加して、コンマで区切られた各イベント名にイベント メソッドのセットを生成します。mix メソッドを 2 回呼び出して生成したプロパティとメソッドのセットにより、fiveseconds イベントをサポートするようにカスタム コントロールを拡張しました。コントロール内部からこの型のイベントをディスパッチするには、dispatchEvent メソッドを呼び出します。

// clockControl.js
...
_tick: function () {
  var now = new Date();
  var sec = now.getSeconds();
  ...
  // Fire the 5 second event
  if (sec % 5 == 0) {
    this.dispatchEvent("fiveseconds", { when: now });
  }
},
...

dispatchEvent の呼び出しは、イベントの名前と、イベントで使用できるオプションの詳細を示すオブジェクトを受け取ります。"when" の値のみを渡していますが、JavaScript は JavaScript なので、必要な値は何でも渡すことができます。ハンドラーのイベントの詳細へのアクセスは、イベント オブジェクトそのものの値の詳細を取り出すときに重要です。

// Do something every 5 seconds
window.clockControl_fiveseconds = function (e) {
  var when = e.detail.when;
  ...
};

今回説明したクラスの名前空間での定義、winControl と要素のプロパティの設定、options オブジェクトの処理、プロパティとメソッドの定義、およびカスタム イベントの定義とディスパッチという、WinJS コントロールの定義の原則は、マイクロソフトの WinJS チームが WinJS コントロールの作成に使用しているのとまったく同じ手法です。WinJS で提供される ui.js ファイルを読むと、お気に入りのコントロールがどのように作成されたかを詳しく知ることができます。

Chris Sells は、Telerik 社の開発者ツール部門のバイス プレジデントです。彼は、この記事の元になった、『Building Windows 8 Apps with JavaScript』(Addison-Wesley Professional、2012 年) の共同執筆者です。Sells と彼の各種プロジェクトの詳細については、sellsbrothers.com (英語) を参照してください。

Brandon Satrom は、Telerik 社の Kendo UI 部門のプログラム マネージャーです。彼は、この記事の元になった、『Building Windows 8 Apps with JavaScript』(Addison-Wesley Professional、2012 年) の共同執筆者です。Twitter (twitter.com/BrandonSatrom、英語) で彼をフォローしてください。

この記事のレビューに協力してくれた技術スタッフの Chris Anderson、Jonathan Antoine、Michael Weinhardt、Shawn Wildermuth、および Josh Williams に心より感謝いたします。