Condividi tramite


Parte 7: Creazione della pagina principale

di Rick Anderson

Scaricare il progetto completato

Creazione della pagina principale

In questa sezione verrà creata la pagina principale dell'applicazione. Questa pagina sarà più complessa della pagina Amministrazione, quindi verrà affrontata in diversi passaggi. Lungo il percorso verranno visualizzate alcune tecniche di Knockout.js più avanzate. Ecco il layout di base della pagina:

Diagramma dell'interazione tra prodotti, carrello, ordini ed elementi dettagli ordine di una pagina principale.

Diagramma dell'interazione tra prodotti, carrello, ordini ed elementi dettagli ordine di una pagina principale. L'elemento products è etichettato GET A P/products con una freccia che punta all'elemento items. L'elemento items è connesso all'elemento orders tramite una freccia con l'etichetta POST A P I/orders. L'elemento orders è connesso all'elemento details con una freccia con l'etichetta GET A P I/orders. L'elemento details è lebeled GET A P I/orders/i d.

  • "Products" contiene una matrice di prodotti.
  • "Cart" contiene una matrice di prodotti con quantità. Facendo clic su "Aggiungi al carrello" viene aggiornato il carrello.
  • "Orders" contiene una matrice di ID ordine.
  • "Dettagli" contiene un dettaglio dell'ordine, che è una matrice di articoli (prodotti con quantità)

Si inizierà definendo un layout di base in HTML, senza data binding o script. Aprire il file Views/Home/Index.cshtml e sostituire tutto il contenuto con quanto segue:

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

Aggiungere quindi una sezione Scripts e creare un modello di visualizzazione vuoto:

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

In base alla progettazione creata in precedenza, il modello di visualizzazione necessita di elementi osservabili per prodotti, carrello, ordini e dettagli. Aggiungere le variabili seguenti all'oggetto AppViewModel :

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

Gli utenti possono aggiungere elementi dall'elenco dei prodotti nel carrello e rimuovere elementi dal carrello. Per incapsulare queste funzioni, verrà creata un'altra classe del modello di visualizzazione che rappresenta un prodotto. Aggiungere il codice seguente 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);
        };
    }
}

La ProductViewModel classe contiene due funzioni utilizzate per spostare il prodotto da e verso il carrello: addItemToCart aggiunge un'unità del prodotto al carrello e removeAllFromCart rimuove tutte le quantità del prodotto.

Gli utenti possono selezionare un ordine esistente e ottenere i dettagli dell'ordine. Questa funzionalità verrà incapsulata in un altro modello di visualizzazione:

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

L'oggetto OrderDetailsViewModel viene inizializzato con un ordine e recupera i dettagli dell'ordine inviando una richiesta AJAX al server.

Si noti anche la total proprietà dell'oggetto OrderDetailsViewModel. Questa proprietà è un tipo speciale di osservabile denominato osservabile calcolato. Come suggerisce il nome, un oggetto osservabile calcolato consente di associare i dati a un valore calcolato, in questo caso il costo totale dell'ordine.

Aggiungere quindi queste funzioni a AppViewModel:

  • resetCart rimuove tutti gli elementi dal carrello.
  • getDetails ottiene i dettagli per un ordine (eseguendo il push di un nuovo OrderDetailsViewModel oggetto nell'elenco details ).
  • createOrder crea un nuovo ordine e svuota il carrello.
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);
            }  
        });
    };
};

Infine, inizializzare il modello di visualizzazione effettuando richieste AJAX per i prodotti e gli ordini:

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, questo è un sacco di codice, ma l'abbiamo compilata passo dopo passo, quindi speriamo che la progettazione sia chiara. È ora possibile aggiungere alcune associazioni Knockout.js al codice HTML.

Prodotti

Ecco le associazioni per l'elenco dei prodotti:

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

Questa operazione scorre la matrice di prodotti e visualizza il nome e il prezzo. Il pulsante "Aggiungi all'ordine" è visibile solo quando l'utente ha effettuato l'accesso.

Il pulsante "Aggiungi all'ordine" chiama addItemToCart l'istanza ProductViewModel del prodotto. In questo modo viene illustrata una bella funzionalità di Knockout.js: quando un modello di visualizzazione contiene altri modelli di visualizzazione, è possibile applicare le associazioni al modello interno. In questo esempio, le associazioni all'interno foreach di vengono applicate a ognuna delle ProductViewModel istanze. Questo approccio è molto più pulito rispetto all'inserimento di tutte le funzionalità in un unico modello di visualizzazione.

Carrello

Ecco le associazioni per il carrello:

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

In questo modo viene scorrere la matrice del carrello e vengono visualizzati il nome, il prezzo e la quantità. Si noti che il collegamento "Rimuovi" e il pulsante "Crea ordine" sono associati alle funzioni del modello di visualizzazione.

Ordini

Ecco le associazioni per l'elenco degli ordini:

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

Viene scorrere gli ordini e viene visualizzato l'ID dell'ordine. L'evento click sul collegamento è associato alla getDetails funzione .

Dettagli ordine

Ecco le associazioni per i dettagli dell'ordine:

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

Questo scorre gli articoli nell'ordine e visualizza il prodotto, il prezzo e la quantità. Il div circostante è visibile solo se la matrice dei dettagli contiene uno o più elementi.

Conclusione

In questa esercitazione è stata creata un'applicazione che usa Entity Framework per comunicare con il database e API Web ASP.NET per fornire un'interfaccia pubblica al livello dati. Usiamo ASP.NET MVC 4 per eseguire il rendering delle pagine HTML e Knockout.js più jQuery per fornire interazioni dinamiche senza ricaricare le pagine.

Risorse aggiuntive: