📚

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

2022/01/01に公開

はじめに

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 を作成します。これはサーバーに渡すデータ (pageIndexpageSize) やサーバーから受け取るデータ (totalCounttotalPageitems) を保持します。この ViewModel にページングを行うための movePreviousmoveNextmoveTo などのメソッドを実装します。サーバーとのやり取りは 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 では SkipTake メソッドを使って必要なデータを取り出します。

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)
        });
    }

}

実行結果

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

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

おわりに

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

Discussion