パート 7: メイン ページの作成

作成者: Rick Anderson

完成したプロジェクトをダウンロードする

メイン ページの作成

このセクションでは、アプリケーションのメイン ページを作成します。 このページは管理者ページよりも複雑になるため、いくつかの手順でアプローチします。 その過程で、より高度な Knockout.js 手法もご紹介します。 ページの基本的なレイアウトを次に示します。

Diagram of interaction between products, cart, orders, and order details elements of a main page.

メイン ページの製品、カート、注文、注文の詳細における要素間の相互作用の図。 products 要素には、items 要素を指す矢印が付いた GET A P I/products というラベルが付けられます。 items 要素は、POST A P I/orders というラベルの矢印によって orders 要素に接続されます。 orders 要素は、GET A P I/orders というラベルの付いた矢印で details 要素に接続されます。 details 要素は GET A P I / orders / i d でラベル付けされます。

  • [製品] には、製品の配列が格納されます。
  • [カート] には、数量を含む製品の配列が格納されます。 [カートに追加] をクリックすると、カートが更新されます。
  • [注文] には、注文 ID の配列が格納されます。
  • [詳細] には、注文の詳細が格納されます。この詳細は、項目の配列 (数量を含む製品) を指します

まず、データ バインディングやスクリプトを使用しない基本的なレイアウトを HTML で定義します。 Views/Home/Index.cshtml ファイルを開き、すべての内容を次のように置き換えます。

<div class="content">
    <!-- List of products -->
    <div class="float-left">
    <h1>Products</h1>
    <ul id="products">
    </ul>
    </div>

    <!-- Cart -->
    <div id="cart" class="float-right">
    <h1>Your Cart</h1>
        <table class="details ui-widget-content">
    </table>
    <input type="button" value="Create Order"/>
    </div>
</div>

<div id="orders-area" class="content" >
    <!-- List of orders -->
    <div class="float-left">
    <h1>Your Orders</h1>
    <ul id="orders">
    </ul>
    </div>

   <!-- Order Details -->
    <div id="order-details" class="float-right">
    <h2>Order #<span></span></h2>
    <table class="details ui-widget-content">
    </table>
    <p>Total: <span></span></p>
    </div>
</div>

次に、Scripts セクションを追加し、空のビュー モデルを作成します。

@section Scripts {
  <script type="text/javascript" src="@Url.Content("~/Scripts/knockout-2.1.0.js")"></script>
  <script type="text/javascript">

    function AppViewModel() {
        var self = this;
        self.loggedIn = @(Request.IsAuthenticated ? "true" : "false");
    }

    $(document).ready(function () {
        ko.applyBindings(new AppViewModel());
    });

  </script>
}

前にスケッチした設計に基づいて、ビュー モデルには製品、カート、注文、および詳細に対する observable 機能が必要です。 AppViewModel オブジェクトに次の変数を追加します。

self.products = ko.observableArray();
self.cart = ko.observableArray();
self.orders = ko.observableArray();
self.details = ko.observable();

ユーザーは、製品リストからカートにアイテムを追加したり、カートからアイテムを削除したりできます。 これらの関数をカプセル化するために、製品を表す別のビュー モデル クラスを作成します。 AppViewModel に次のコードを追加します。

function AppViewModel() {
    // ...

    // NEW CODE
    function ProductViewModel(root, product) {
        var self = this;
        self.ProductId = product.Id;
        self.Name = product.Name;
        self.Price = product.Price;
        self.Quantity = ko.observable(0);

        self.addItemToCart = function () {
            var qty = self.Quantity();
            if (qty == 0) {
                root.cart.push(self);
            }
            self.Quantity(qty + 1);
        };

        self.removeAllFromCart = function () {
            self.Quantity(0);
            root.cart.remove(self);
        };
    }
}

この ProductViewModel クラスには、カート間で製品を移動するために使用される 2 つの関数が含まれています。addItemToCart は製品を 1 ユニット単位でカートに追加し、removeAllFromCart は製品のすべての数量を削除します。

ユーザーは既存の注文を選択し、注文の詳細を取得できます。 この機能を別のビュー モデルにカプセル化します。

function AppViewModel() {
    // ...

    // NEW CODE
    function OrderDetailsViewModel(order) {
        var self = this;
        self.items = ko.observableArray();
        self.Id = order.Id;

        self.total = ko.computed(function () {
            var sum = 0;
            $.each(self.items(), function (index, item) {
                sum += item.Price * item.Quantity;
            });
            return '$' + sum.toFixed(2);
        });

        $.getJSON("/api/orders/" + order.Id, function (order) {
            $.each(order.Details, function (index, item) {
                self.items.push(item);
            })
        });
    };
}

OrderDetailsViewModel は注文で初期化され、サーバーに AJAX 要求を送信して注文の詳細をフェッチします。

また、OrderDetailsViewModeltotal プロパティにも注目してください。 このプロパティは、computed observable と呼ばれる特殊な種類の observable です。 名前が示すように、computed observable 値を使用すると、computed observable (この場合は注文の合計コスト) にデータをバインドできます。

次に、次の関数を AppViewModel に追加します。

  • resetCart は、カートからすべてのアイテムを削除します。
  • getDetails は、注文の詳細を取得します (details リストに新しい OrderDetailsViewModel をプッシュします)。
  • createOrder は新しい注文を作成し、カートを空にします。
function AppViewModel() {
    // ...

    // NEW CODE
    self.resetCart = function() {
        var items = self.cart.removeAll();
        $.each(items, function (index, product) {
            product.Quantity(0);
        });
    }

    self.getDetails = function (order) {
        self.details(new OrderDetailsViewModel(order));
    }

    self.createOrder = function () {
        var jqxhr = $.ajax({
            type: 'POST',
            url: "api/orders",
            contentType: 'application/json; charset=utf-8',
            data: ko.toJSON({ Details: self.cart }),
            dataType: "json",
            success: function (newOrder) {
                self.resetCart();
                self.orders.push(newOrder);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                self.errorMessage(errorThrown);
            }  
        });
    };
};

最後に、製品と注文に対して AJAX 要求を行って、ビュー モデルを初期化します。

function AppViewModel() {
    // ...

    // NEW CODE
    // Initialize the view-model.
    $.getJSON("/api/products", function (products) {
        $.each(products, function (index, product) {
            self.products.push(new ProductViewModel(self, product));
        })
    });

    $.getJSON("api/orders", self.orders);
};

ここにはたくさんのコードがありますが、ステップ バイ ステップで構築したので、設計が明確であることを願っています。 これで、Knockout.jsバインドを HTML に追加できます。

製品

製品一覧のバインドを次に示します。

<ul id="products" data-bind="foreach: products">
    <li>
        <div>
            <span data-bind="text: Name"></span> 
            <span class="price" data-bind="text: '$' + Price"></span>
        </div>
        <div data-bind="if: $parent.loggedIn">
            <button data-bind="click: addItemToCart">Add to Order</button>
        </div>
    </li>
</ul>

これにより、products 配列が反復処理され、名前と価格が表示されます。 [注文に追加] ボタンは、ユーザーがログインしている場合にのみ表示されます。

[注文に追加] ボタンは、製品の ProductViewModel インスタンスの addItemToCart を呼び出します。 これは、Knockout.js の優れた機能を示しています。ビュー モデルに他のビュー モデルが含まれている場合は、内部モデルにバインドを適用できます。 この例では、foreach 内部のバインドが各 ProductViewModel インスタンスに適用されます。 このアプローチは、すべての機能を単一のビュー モデルに配置するよりもずっとすっきりしています。

カート

カートのバインドを次に示します。

<div id="cart" class="float-right" data-bind="visible: cart().length > 0">
<h1>Your Cart</h1>
    <table class="details ui-widget-content">
    <thead>
        <tr><td>Item</td><td>Price</td><td>Quantity</td><td></td></tr>
    </thead>    
    <tbody data-bind="foreach: cart">
        <tr>
            <td><span data-bind="text: $data.Name"></span></td>
            <td>$<span data-bind="text: $data.Price"></span></td>
            <td class="qty"><span data-bind="text: $data.Quantity()"></span></td>
            <td><a href="#" data-bind="click: removeAllFromCart">Remove</a></td>
        </tr>
    </tbody>
</table>
<input type="button" data-bind="click: createOrder" value="Create Order"/>

これにより、カート配列が反復処理され、名前、価格、数量が表示されます。 [削除] リンクと [注文の作成] ボタンはビュー モデル関数にバインドされていることに注意してください。

Orders (注文)

注文リストのバインドを次に示します。

<h1>Your Orders</h1>
<ul id="orders" data-bind="foreach: orders">
<li class="ui-widget-content">
    <a href="#" data-bind="click: $root.getDetails">
        Order # <span data-bind="text: $data.Id"></span></a>
</li>
</ul>

これにより、注文が反復処理され、注文 ID が表示されます。 リンクのクリック イベントは getDetails 関数にバインドされます。

注文の詳細

注文の詳細のバインドを次に示します。

<div id="order-details" class="float-right" data-bind="if: details()">
<h2>Order #<span data-bind="text: details().Id"></span></h2>
<table class="details ui-widget-content">
    <thead>
        <tr><td>Item</td><td>Price</td><td>Quantity</td><td>Subtotal</td></tr>
    </thead>    
    <tbody data-bind="foreach: details().items">
        <tr>
            <td><span data-bind="text: $data.Product"></span></td>
            <td><span data-bind="text: $data.Price"></span></td>
            <td><span data-bind="text: $data.Quantity"></span></td>
            <td>
                <span data-bind="text: ($data.Price * $data.Quantity).toFixed(2)"></span>
            </td>
        </tr>
    </tbody>
</table>
<p>Total: <span data-bind="text: details().total"></span></p>
</div>

これにより、注文の項目が反復処理され、製品、価格、数量が表示されます。 周囲の div は、details 配列に 1 つ以上の項目が含まれている場合にのみ表示されます。

まとめ

このチュートリアルでは、Entity Framework を使用してデータベースと通信し、ASP.NET Web API を使用してデータ レイヤーの上に公開インターフェイスを提供するるアプリケーションを作成しました。 ASP.NET MVC 4 を使用して HTML ページをレンダリングし、Knockout.js と jQuery を使用して、ページの再読み込みなしで動的な対話を提供します。

その他のリソース: