第 7 部分:创建主页

作者:Rick Anderson

下载已完成项目

创建主页面

在本部分中,将创建main应用程序页。 此页面将比管理员页面更复杂,因此我们将通过几个步骤对其进行处理。 在此过程中,你将看到一些更高级Knockout.js技术。 下面是页面的基本布局:

main页面的产品、购物车、订单和订单详细信息元素之间的交互关系图。

main页面的产品、购物车、订单和订单详细信息元素之间的交互关系图。 products 元素被标记为 GET A P I / products,箭头指向 items 元素。 items 元素通过标记为 POST A P I / orders 的箭头连接到 orders 元素。 orders 元素连接到带有标记为 GET A P I / orders 的箭头的 details 元素。 details 元素为 lebeled GET A P I / orders / i d。

  • “产品”包含一系列产品。
  • “购物车”包含一系列具有数量的产品。 单击“添加到购物车”可更新购物车。
  • “Orders”包含订单 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>

接下来,添加“脚本”部分并创建一个空的视图模型:

@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>
}

根据前面草图的设计,我们的视图模型需要产品、购物车、订单和详细信息的可观测内容。 将以下变量添加到 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 包含用于将产品移向和移出购物车的两个函数: addItemToCart 将产品的一个单位添加到购物车,并 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 请求来提取订单详细信息。

另请注意 上的 totalOrderDetailsViewModel属性。 此属性是一种特殊的可观测类型,称为计算可观测。 顾名思义,计算的可观测对象允许将数据绑定到计算值,在本例中为订单的总成本。

接下来,将这些函数添加到 AppViewModel

  • resetCart 从购物车中删除所有项目。
  • getDetails 通过将新的 OrderDetailsViewModel 推送到 details 列表) 来获取订单 (的详细信息。
  • 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);
};

好吧,这是很多代码,但我们逐步构建了它,所以希望设计是明确的。 现在,我们可以向 HTML 添加一些Knockout.js绑定。

产品

下面是产品列表的绑定:

<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。 链接上的 click 事件绑定到 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 才可见。

结论

在本教程中,你创建了一个使用 Entity Framework 与数据库通信的应用程序,并 ASP.NET Web API在数据层之上提供面向公众的接口。 我们使用 ASP.NET MVC 4 来呈现 HTML 页面,并使用 Knockout.js plus jQuery 来提供动态交互,而无需重新加载页面。

其他资源: