Partie 7 : Création de la page principale

par Rick Anderson

Télécharger le projet terminé

Création de la page principale

Dans cette section, vous allez créer la page d’application main. Cette page étant plus complexe que la page Administration, nous allons l’aborder en plusieurs étapes. En cours de route, vous verrez des techniques de Knockout.js plus avancées. Voici la disposition de base de la page :

Diagramme d’interaction entre les produits, le panier, les commandes et les éléments de détails de commande d’une page de main.

Diagramme d’interaction entre les produits, le panier, les commandes et les éléments de détails de commande d’une page de main. L’élément products est étiqueté GET A IP / products avec une flèche pointant vers l’élément items. L’élément items est connecté à l’élément orders par une flèche intitulée POST A IP / orders. L’élément orders est connecté à l’élément details avec une flèche intitulée GET A IP / orders. L’élément details est étiqueté GET A P I / orders / i d.

  • « Products » contient une gamme de produits.
  • Le « panier » contient une gamme de produits avec des quantités. Cliquez sur « Ajouter au panier » pour mettre à jour le panier.
  • « Orders » contient un tableau d’ID de commande.
  • « Détails » contient un détail de commande, qui est un tableau d’articles (produits avec des quantités)

Nous allons commencer par définir une disposition de base au format HTML, sans liaison de données ni script. Ouvrez le fichier Views/Home/Index.cshtml et remplacez tout le contenu par ce qui suit :

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

Ensuite, ajoutez une section Scripts et créez un modèle d’affichage vide :

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

Basé sur la conception esquissée précédemment, notre modèle d’affichage a besoin d’observables pour les produits, le panier, les commandes et les détails. Ajoutez les variables suivantes à l’objet AppViewModel :

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

Les utilisateurs peuvent ajouter des éléments de la liste de produits dans le panier et supprimer des éléments du panier. Pour encapsuler ces fonctions, nous allons créer une autre classe de modèle d’affichage qui représente un produit. Ajoutez le code suivant à 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 contient deux fonctions utilisées pour déplacer le produit vers et depuis le panier : addItemToCart ajoute une unité du produit au panier et removeAllFromCart supprime toutes les quantités du produit.

Les utilisateurs peuvent sélectionner une commande existante et obtenir les détails de la commande. Nous allons encapsuler cette fonctionnalité dans un autre modèle d’affichage :

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

le OrderDetailsViewModel est initialisé avec une commande et extrait les détails de la commande en envoyant une requête AJAX au serveur.

Notez également la total propriété sur le OrderDetailsViewModel. Cette propriété est un type spécial d’observable appelé observable calculé. Comme son nom l’indique, une observable calculée vous permet de lier des données à une valeur calculée, dans ce cas, le coût total de la commande.

Ensuite, ajoutez ces fonctions à AppViewModel:

  • resetCart supprime tous les articles du panier.
  • getDetails obtient les détails d’une commande (en envoyant un nouveau OrderDetailsViewModel dans la details liste).
  • createOrder crée une nouvelle commande et vide le panier.
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);
            }  
        });
    };
};

Enfin, initialisez le modèle d’affichage en effectuant des demandes AJAX pour les produits et les commandes :

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, c’est beaucoup de code, mais nous l’avons créé pas à pas, donc j’espère que la conception est claire. Nous pouvons maintenant ajouter des liaisons Knockout.js au code HTML.

Produits

Voici les liaisons pour la liste des produits :

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

Cela itère sur le tableau de produits et affiche le nom et le prix. Le bouton « Ajouter à la commande » n’est visible que lorsque l’utilisateur est connecté.

Le bouton « Ajouter à la commande » appelle addItemToCart sur le ProductViewModel instance du produit. Cela illustre une fonctionnalité intéressante de Knockout.js : lorsqu’un modèle d’affichage contient d’autres modèles d’affichage, vous pouvez appliquer les liaisons au modèle interne. Dans cet exemple, les liaisons dans le foreach sont appliquées à chacune des ProductViewModel instances. Cette approche est beaucoup plus propre que de placer toutes les fonctionnalités dans un seul modèle d’affichage.

Panier

Voici les liaisons pour le panier :

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

Cela itère sur le tableau de paniers et affiche le nom, le prix et la quantité. Notez que le lien « Supprimer » et le bouton « Créer une commande » sont liés aux fonctions de modèle d’affichage.

Orders (Commandes)

Voici les liaisons pour la liste des commandes :

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

Cela itère sur les commandes et affiche l’ID de commande. L’événement click sur le lien est lié à la getDetails fonction .

Détails de la commande

Voici les liaisons pour les détails de la commande :

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

Cela itère sur les éléments dans l’ordre et affiche le produit, le prix et la quantité. Le div environnant n’est visible que si le tableau de détails contient un ou plusieurs éléments.

Conclusion

Dans ce tutoriel, vous avez créé une application qui utilise Entity Framework pour communiquer avec la base de données et API Web ASP.NET pour fournir une interface publique au-dessus de la couche de données. Nous utilisons ASP.NET MVC 4 pour afficher les pages HTML, et Knockout.js plus jQuery pour fournir des interactions dynamiques sans recharge de page.

Ressources supplémentaires :