Parte 7: criando a página principal

por Mike Wasson

Baixar projeto concluído

Criação da página principal

Nesta seção, você criará a página principal do aplicativo. Essa página será mais complexa do que a página de administração, portanto, vamos abordá-la em várias etapas. Ao longo do caminho, você verá algumas técnicas mais avançadas de Knockout. js. Este é o layout básico da página:

  • "Produtos" contém uma matriz de produtos.
  • "Carrinho" contém uma matriz de produtos com quantidades. Clicar em "adicionar ao carrinho" atualizará o carrinho.
  • "Orders" mantém uma matriz de IDs de pedido.
  • "Detalhes" contém um detalhe do pedido, que é uma matriz de itens (produtos com quantidades)

Vamos começar definindo um layout básico em HTML, sem vinculação de dados ou script. Abra o arquivo views/home/index. cshtml e substitua todo o conteúdo pelo seguinte:

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

Em seguida, adicione uma seção scripts e crie um modelo de exibição vazio:

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

Com base no design esboçado anteriormente, nosso modelo de exibição precisa de observáveis para produtos, carrinho, pedidos e detalhes. Adicione as seguintes variáveis ao objeto AppViewModel:

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

Os usuários podem adicionar itens da lista de produtos no carrinho e remover itens do carrinho. Para encapsular essas funções, criaremos outra classe View-Model que representa um produto. Adicione o seguinte código a 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);
        };
    }
}

A classe ProductViewModel contém duas funções que são usadas para mover o produto de e para o carrinho: addItemToCart adiciona uma unidade do produto ao carrinho e removeAllFromCart remove todas as quantidades do produto.

Os usuários podem selecionar um pedido existente e obter os detalhes do pedido. Encapsularemos essa funcionalidade em outro View-Model:

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

O OrderDetailsViewModel é inicializado com um pedido e busca os detalhes do pedido enviando uma solicitação AJAX ao servidor.

Além disso, observe a propriedade total no OrderDetailsViewModel. Essa propriedade é um tipo especial de observável chamado de observed calculado. Como o nome indica, um observed calculado permite que você vincule dados a um valor—calculado nesse caso, o custo total do pedido.

Em seguida, adicione essas funções a AppViewModel:

  • resetCart remove todos os itens do carrinho.
  • getDetails Obtém os detalhes de um pedido (enviando um novo OrderDetailsViewModel para a lista de details).
  • createOrder cria um novo pedido e esvazia o carrinho.
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);
            }  
        });
    };
};

Por fim, inicialize o modelo de exibição fazendo solicitações AJAX para os produtos e pedidos:

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

OK, isso é muito código, mas nós o criamos passo a passo, então espero que o design esteja claro. Agora, podemos adicionar algumas associações knockout. js ao HTML.

Produtos

Aqui estão as associações para a lista de produtos:

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

Isso itera na matriz de produtos e exibe o nome e o preço. O botão "adicionar à ordem" só fica visível quando o usuário está conectado.

O botão "adicionar à ordem" chama addItemToCart na instância de ProductViewModel do produto. Isso demonstra um bom recurso de Knockout. js: quando um View-Model contém outros modos de exibição-modelos, você pode aplicar as associações ao modelo interno. Neste exemplo, as associações dentro do foreach são aplicadas a cada uma das instâncias de ProductViewModel. Essa abordagem é muito mais limpa do que colocar toda a funcionalidade em um único modelo de exibição.

Carrinho

Aqui estão as associações para o carrinho:

<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"/>

Isso itera na matriz de carrinhos e exibe o nome, o preço e a quantidade. Observe que o link "remover" e o botão "criar ordem" estão associados às funções de modelo de exibição.

Solicitar

Aqui estão as associações para a lista 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>

Isso itera sobre os pedidos e mostra a ID do pedido. O evento de clique no link está associado à função getDetails.

Detalhes do pedido

Aqui estão as associações para os detalhes do pedido:

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

Isso itera sobre os itens na ordem e exibe o produto, o preço e a quantidade. A div ao redor só será visível se a matriz de detalhes contiver um ou mais itens.

Conclusão

Neste tutorial, você criou um aplicativo que usa Entity Framework para se comunicar com o banco de dados e ASP.NET Web API para fornecer uma interface voltada para o público sobre a camada de dado. Usamos o ASP.NET MVC 4 para renderizar as páginas HTML e knockout. js Plus jQuery para fornecer interações dinâmicas sem recargas de página.

Recursos adicionais: