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 rispetto alla pagina Amministratore, quindi verrà affrontata in diversi passaggi. Lungo il percorso, vedrai 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 relativi ai dettagli dell'ordine di una pagina principale. L'elemento products è etichettato come GET API/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 etichettata come GET API/orders. L'elemento dettagli è etichettato come GET A P I/orders/i d.

  • "Products" contiene una serie di prodotti.
  • "Carrello" 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 tracciata in precedenza, il modello di visualizzazione necessita di 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);
            })
        });
    };
}

Il OrderDetailsViewModel viene avviato con un ordine e recupera i dettagli dell'ordine inviando una richiesta AJAX al server.

Si noti anche la total proprietà in 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 (inserendo un nuovo OrderDetailsViewModel 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 alcuni binding 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>

Questo scorre l'array di prodotti e visualizza il nome e il prezzo. Il pulsante "Aggiungi all'ordine" è visibile solo quando l'utente ha eseguito l'accesso.

Il pulsante "Aggiungi all'ordine" chiama addItemToCart sull'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 di foreach vengono applicate a ognuna delle istanze ProductViewModel. Questo approccio è molto più pulito rispetto all'inserimento di tutte le funzionalità in un unico modello di visualizzazione.

Carrello

Ecco le configurazioni 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 iterato sull'array 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 (o assegnazioni) 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>

Questo itera sugli ordini e mostra l'ID dell'ordine. L'evento click sul collegamento è associato alla getDetails funzione.

Dettagli ordine

Ecco i collegamenti 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 ASP.NET API Web per fornire un'interfaccia pubblica sopra il 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 la pagina.

Risorse aggiuntive: