からめもぶろぐ。

SharePoint が得意なフレンズなんだね!すごーい!

knockout.js を使ってページングを実装してみる

knockout.js がいい感じに便利で楽しいので、いろいろ試してみました。今回はページングを実装してみます。

サンプル コード

Shared/_Layout.cshtml

knockout.js を呼んでおくのを忘れずに。knockout.js は NuGet で入手できます。

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>

Home/Index.cshtml

ページングを行うための ViewModel を作成します。これはサーバーに渡すデータ (pageIndex、pageSize) やサーバーから受け取るデータ (totalCount、totalPage、items) を保持します。この ViewModel にページングを行うための movePrevious、moveNext、moveTo などのメソッドを実装します。サーバーとのやり取りは load メソッドで行います。
HTML では knockout.js の作法に従って data-bind 属性を記述していきます。click イベントも data-bind に書けてしまうのが嬉しいところです。今回は実装していませんが、先頭や最後のページのときにナビゲーションの [前へ] や [次へ] を無効化したい場合は、data-bind="if:" や data-bind="notif:" を使うのが便利です。

@{
    ViewBag.Title = "ホーム";
}
<div>
    <div>
        <a href="javascript:void(0)" data-bind="click: movePrevious">前へ</a>
        <a href="javascript:void(0)" data-bind="click: moveNext">次へ</a>
    </div>
    <hr />
    <div data-bind="foreach: items">
        <div>名前: <span data-bind="text: Name"></span></div>
        <div>誕生日: <span data-bind="text: Birthday"></span></div>
        <div>国籍: <span data-bind="text: Nationality"></span></div>
        <hr />
    </div>
</div>
<script type="text/javascript">
    $(function () {
        var viewModel = {
            // サーバーのURL
            url: "@Url.Content("~/Home/Index")",
            // 現在のページ番号
            pageIndex: ko.observable(1),
            // ページに表示する件数
            pageSize: ko.observable(3),
            // 総件数
            totalCount: ko.observable(0),
            // 総ページ数
            totalPage: ko.observable(0),
            // 結果のリスト
            items: ko.observableArray([]),
            // 前のページに移動するメソッド
            movePrevious: function () {
                if (this.pageIndex() > 1) {
                    this.moveTo(this.pageIndex() - 1);
                }
            },
            // 次のページに移動するメソッド
            moveNext: function () {
                if (this.pageIndex() < this.totalPage()) {
                    this.moveTo(this.pageIndex() + 1);
                }
            },
            // 指定したページに移動するメソッド
            moveTo: function (index) {
                this.pageIndex(index);
                this.load();
            },
            // サーバーに問い合わせるメソッド
            load: function () {
                // パラメーターはサーバーでは FormCollection で受け取る
                var param = {
                    pageIndex: this.pageIndex,
                    pageSize: this.pageSize
                };
                // コールバック
                var callback = function (data) {
                    viewModel.totalCount(data.TotalCount);
                    viewModel.totalPage(data.TotalPage);
                    viewModel.items.removeAll();
                    $.each(data.Items, function (i, e) { viewModel.items.push(e) });
                };
                // POST でリクエストを投げる
                $.post(this.url, param, callback, "json");
            }
        };
        ko.applyBindings(viewModel);
        viewModel.load();
    })
</script>

HomeController.cs

Controller では Skip と Take メソッドを使って必要なデータを取り出します。

namespace MvcApplication1.Controllers {

    public class HomeController : Controller {

        public ActionResult Index() {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Index(FormCollection collection) {
            var data = new[] {
                new { Name = "藍澤優", Birthday = "4/30", Nationality = "台湾" },
                new { Name = "藍澤葵", Birthday = "11/12", Nationality = "台湾" },
                new { Name = "藍澤玲", Birthday = "7/10", Nationality = "台湾" },
                new { Name = "藍澤光", Birthday = "9/27", Nationality = "台湾" },
                new { Name = "窓辺ななみ", Birthday = "4/6", Nationality = "日本" },
                new { Name = "クラウディア窓辺", Birthday = "11/20", Nationality = "アメリカ" },
                new { Name = "クロード窓辺", Birthday = "4/4", Nationality = "アメリカ" },
                new { Name = "ウェブマトリクスマン", Birthday = "不明", Nationality = "日本" },
            };
            var pageIndex = int.Parse(collection["pageIndex"]);
            var pageSize = int.Parse(collection["pageSize"]);
            return Json(new {
                TotalCount = data.Count(),
                TotalPage = (int)Math.Ceiling((double)data.Count() / pageSize),
                Items = data.Skip(pageSize * (pageIndex - 1)).Take(pageSize),
            });
        }

    }

}

実行

実行してみるとこんな感じで表示されます。

f:id:karamem0:20160625224858p:plain

[次へ] をクリックしてみるとページが切り替わっているのがわかるかと思います。

f:id:karamem0:20160625224849p:plain

まとめ

今回のサンプルではナビゲーションが貧弱ですが、頑張ればページ番号にリンクをつけたりすることもできます。割とよく使うテクニックだと思われるので、ViewModel をクラス化してしまえば、かなり便利になると思います。