Часть 7. Создание главной страницы
Рик Андерсон
Создание главной страницы
В этом разделе вы создадите страницу приложения main. Эта страница будет более сложной, чем страница Администратор, поэтому мы рассмотрим ее в нескольких шагах. Попутно вы увидите некоторые более сложные методы Knockout.js. Ниже приведен базовый макет страницы:
Схема взаимодействия между элементами продуктов, корзины, заказов и сведений о заказах на странице main. Элемент products помечается как GET A P I/products со стрелкой, указывающей на элемент items. Элемент items соединяется с элементом orders с помощью стрелки с меткой POST A P I/orders. Элемент orders подключается к элементу details с помощью стрелки GET A P I/orders. Элемент details является lebeled GET A P I / orders / i d.
- "Products" содержит массив продуктов.
- "Корзина" содержит массив продуктов с количеством. При нажатии кнопки "Добавить в корзину" корзина обновляется.
- "Orders" содержит массив идентификаторов заказов.
- "Details" содержит сведения о заказе, которые являются массивом элементов (товаров с количествами)
Начнем с определения базового макета в 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 на сервер.
Кроме того, обратите внимание на total
свойство в OrderDetailsViewModel
. Это свойство является особым типом наблюдаемого объекта, называемого вычисляемым наблюдаемым. Как следует из названия, вычисляемый наблюдаемый объект позволяет привязать данные к вычисляемому значению, в данном случае к общей стоимости заказа.
Затем добавьте эти функции в 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);
};
Хорошо, это много кода, но мы создали его шаг за шагом, так что, надеюсь, дизайн ясен. Теперь можно добавить некоторые 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 и отображаются имя и цена. Кнопка "Добавить в заказ" отображается только при входе пользователя.
Кнопка "Добавить в заказ" вызывает addItemToCart
ProductViewModel
экземпляр для продукта. Это демонстрирует хорошую функцию 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"/>
При этом выполняется итерация по массиву корзины и отображаются имя, цена и количество. Обратите внимание, что ссылка "Удалить" и кнопка "Создать заказ" привязаны к функциям модели просмотра.
Заказы
Ниже приведены привязки для списка заказов.
<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>
При этом выполняется итерацию по заказам и отображается идентификатор заказа. Событие щелчка по ссылке привязано к 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 для взаимодействия с базой данных, а веб-API ASP.NET предоставить общедоступный интерфейс поверх уровня данных. Мы используем ASP.NET MVC 4 для отрисовки HTML-страниц и Knockout.js плюс jQuery для обеспечения динамических взаимодействий без перезагрузки страниц.
Дополнительные ресурсы: