ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

WinJS.UI.ListView の基本的な使い方 (項目の大きさの指定方法と要素の生成方法)

Windows ストアアプリを JavaScript で開発する際の話です。

Windows 8 の特徴の一つでもあるタイルを並べたような表示を行うための WinJS コントロールとして WinJS.UI.ListView があります。 理解してしまうと特に難しいところはないのですが、プロジェクトテンプレートにもともと組み込まれている WinJS.UI.ListView を見よう見真似で調整しようとしても難しかったりするので、基本的な使い方を書いておきます。

WinJS.UI.ListView の基本的な使い方

当然ながら、最初に WinJS.UI.ListView オブジェクトを生成する必要があります。 HTML の読み込み直後から WinJS.UI.ListView が存在して良いのであれば、以下のように HTML 中に data-win-control 属性 を使って WinJS.UI.ListView コントロールを宣言的に書いておけばよいでしょう。 (JavaScript 側で動的に WinJS.UI.ListView オブジェクトを生成してもよいです。)

<div data-win-control="WinJS.UI.ListView" class="sample-list-view"></div>

あとは、HTML の記述によって生成される WinJS.UI.ListView オブジェクトを JavaScript 側で取得して、WinJS.Binding.List オブジェクト をデータソースとして結びつけてやれば、基本的には完了です。

// WinJS.UI.ListView オブジェクトの取得 (この前に WinJS.UI.processAll() は実行済みとする)
var listView = document.querySelector(".sample-list-view").winControl;

// ListView オブジェクトに結びつけるリストの生成
var list = new WinJS.Binding.List();

// データソースとしてリストを ListView に結びつける
listView.itemDataSource = list.dataSource;

// 後はリストにデータを追加していくと, それに応じて ListView の表示が変更される
list.push({ name: "シンジ" });
list.push({ name: "カヲル" });
list.push({ name: "レイ" });
list.push({ name: "アスカ" });
list.push({ name: "加持リョウジ" });
list.push({ name: "葛城ミサト" });

結果として、以下のような表示になります。 各項目の表示方法を指定していないため、オブジェクトを文字列化したものがそのまま表示されています (ListView 全体の大きさやボーダー、項目の背景色などは適当に CSS を当てています)。 また、各項目は原則としてすべて同一サイズになる *1 のですが、今回の場合、特に項目の大きさを指定していないため、最初の項目の大きさに揃ってしまっており、「加持リョウジ」 や 「葛城ミサト」 ははみ出してしまっています。

f:id:nobuoka:20121101011954p:plain

各項目を生成するためのテンプレートを指定する

上の基本的な使い方の例では、各項目の中身としてオブジェクトを文字列化したものがそのまま表示されていました。 各項目を生成するためのテンプレートは、WinJS.UI.ListView#itemTemplate プロパティ に設定することができます。 このプロパティに設定できる値の種類としてもっとも簡単なものは WinJS.Binding.Template オブジェクト に対応する HTML 要素です。

注) ドキュメントでは、itemTemplate プロパティに WinJS.Binding.Template オブジェクトを設定できると書かれていますが、実際にそれをすると項目生成時にエラーが発生します。 詳細は公式フォーラムに書きましたので、興味がありましたらご覧ください。

HTML に以下のように WinJS.Binding.Template によるテンプレートを定義しておいて、

<div data-win-control="WinJS.Binding.Template" class="sample-item-template">
  <div class="name-box">
    名前 : <span data-win-bind="textContent: name"></span>
  </div>
</div>

JavaScript でこの要素を取得し、itemTemplate プロパティに設定します。

// WinJS.Binding.Template オブジェクトに対応する HTML 要素を取得
var templateElem = document.querySelector(".sample-item-template");
// itemTemplate プロパティに設定
listView.itemTemplate = templateElem;

このテンプレートを用いて項目ごとの HTML 要素が生成されるようになります。 その結果、以下のような見た目になります。

f:id:nobuoka:20121101013917p:plain

生成される HTML は、ざっくり書くと以下のような感じになります。

  <div class="win-container">
    <div class="win-item win-template">
      <div class="name-box">
        名前 : <span>...</span>
      </div>
    </div>
  </div>
  <div class="win-container">
    ... (別の項目) ...
  </div>

項目テンプレートとして関数を指定する

WinJS.UI.ListView#itemTemplate プロパティには関数を指定することもできます。 関数の引数として、Promise で wrap された項目の情報を表すオブジェクトと、ListView で不要になった項目の HTML 要素 (存在する場合のみ?; 不要になった要素を再利用することで効率を高める場合に利用されることが意図されており、特に効率を考えない場合は無視して良い) が渡されます。 関数の返り値としては、項目を表す HTML 要素 (Promise で wrap されていてもよい) か、以下のようなオブジェクトが期待されます。

{
    element: <項目を表す HTML 要素>,
    renderComplete: <項目を表す HTML 要素の中身の生成が完了したときに呼び出される Promise>
}

なので、関数を使って上の例と同じ結果を生成するためには、以下のようにします。

// WinJS.Binding.Template オブジェクトを取得 (関数の中で使うため)
var template = document.querySelector(".sample-item-template").winControl;
// itemTemplate プロパティに関数を設定
listView.itemTemplate = function (itemPromise) { // 第 2 引数は無視する
    return itemPromise.then(function (item) { // item は, 項目の情報を表すオブジェクト
        // item.data に, list に push したオブジェクト (今回の例だと { name: "シンジ" } みたいなの) と同一のデータが入っている
        return template.render(item.data);
    }).
    then(function (elem) { // elem は, template.render の結果生成される HTML 要素
        // この elem が項目を表す HTML 要素で, ListView によって "win-item" クラスが自動的に追加される
        // 今回は何もしないが, "win-item" のクラスをもつ要素に独自のクラスをもたせないときはここで追加すればよい
        return elem;
    });
};

項目のテンプレートとして関数を使うと、項目の中身に応じて異なる HTML 要素を生成するなど、より自由に項目の中身をいじることができます。

各項目の大きさと隙間を指定する

ここまでの例では項目の大きさを明示的には指定していませんでした。 ここでは、CSS により項目の大きさや項目間の隙間を決める方法を示します。 上でも示したように、各項目は以下のように "win-container" というクラスをもつ div 要素と、"win-item" というクラスをもつ要素で構成されます。

  <div class="win-container" style="...">
    <div class="win-item win-template">
      <span>項目の中身...</span>
    </div>
  </div>
  <div class="win-container" style="...">
    <div class="win-item win-template">
      <span>項目の中身...</span>
    </div>
  </div>
  <div class="win-container" style="...">
    <div class="win-item win-template">
      <span>項目の中身...</span>
    </div>
  </div>

GridLayout の場合

WinJS.UI.ListView を GridLayout (ListView のデフォルトレイアウト) で表示する場合、項目の大きさは win-item の要素の width と height で指定してやります。 また、項目間の隙間は win-container の要素の margin で指定します。

/* .win-horizontal を入れているのは //Microsoft.WinJS.1.0/css/ui-dark.css などにおける CSS 指定を参考にしたため */
.win-listview.sample-list-view > .win-horizontal .win-container {
    margin: 10px;
    background-color: #333333;
}
.win-listview.sample-list-view .win-item {
    box-sizing: border-box;
    height: 45px;
    padding: 3px;
    width: 140px;
}

例えば上のような CSS 指定を行うと、以下のような表示になります。

f:id:nobuoka:20121101021513p:plain

ListLayout の場合

WinJS.UI.ListView のレイアウトとしては、GridLayout の他に ListLayout もあります。 ListLayout は、縦方向に一列に項目が並ぶレイアウトです。 ListView を ListLayout で表示するには、WinJS.UI.ListView#layout プロパティWinJS.UI.ListLayout オブジェクト を設定します。

listView.layout = new WinJS.UI.ListLayout();

ListLayout の場合、項目の縦の大きさ GridLayout の場合と同じく win-item の要素の height で指定すればよいです。 一方で、項目の横幅は自動的に最大の値になるので、指定する必要はありません (逆に指定するとおかしくなる)。 すなわち、win-container の左右の margin を指定すると、自動的に残りの使える領域が要素の横幅になります。

.win-listview.sample-list-view > .win-vertical .win-container {
    margin: 10px 25px 10px 10px;
    background-color: #333333;
}
.win-listview.sample-list-view .win-item {
    box-sizing: border-box;
    height: 45px;
    padding: 3px;
}

例えば上のような CSS を書くと、下のような表示になります。

f:id:nobuoka:20121101024727p:plain

おわり

大体ここら辺のことをちゃんと理解しておけば既存の WinJS.UI.ListView をそれなりにいじれるんじゃないかなーと思います。

*1:基本サイズを決めて、その定数倍のサイズを表示できるようにもできます