Micro App Angular 9 : Angular Store en Français = Magasin Angular.

Introduction

logo Angular Store – Magasin Angular


Aujourd’hui, je ne vais pas créer ma propre application, mais analyser l’application d’un développeur Anglophone : Angular Store.

On peut voir sa publication ici :http://www.codeproject.com/Articles/576246/A-Shopping-Cart-Application-Built-with-AngularJS

Pour le tester sur mon serveur, c’est ici .

Son code est plus ou moins à la norme « Bower », c’est à dire avec une arborescence séparant le contrôleurs, les vues, le modèle et les directives. Mais il n’est pas minimifié.

Je vais tenter de le passer en français au maximum et de le customiser un peu si possible afin de le rendre générique pour moi.

Cependant, le but est seulement de voir un peu comment les autres organisent leur code ANGULARJS.

Mon but, en dév, serait d’inventer des applications inconnues et des tableaux de bords d’ingéniérie, et plutôt pas de faire dun énième magasin bien sur(Des cms le font très bien), mais il faut en faire un peu. AngularJs provoque un gain de productivité incroyable et ce n’est pas du tout une légende, même avec mon xp de 4 mois , je m’en rends compte.

Impressions, Notes


  • Ce code date un peu, du coup, on sent qu’il était en transition  de Jquery à Angular, il a intégré un « truc » nommé Stripe et une librairie Jquery de formulaires.  Je ne vois pas l’intérêt, vu comment Angular traite bien les formulaires.
  • Il n’y a pas de carroussel.
  • La fonctionnalité Paypal et Google Pay fonctionne très bien, et comme il dit, c’est indépendant, et on ne prends pas le risque de conserver des numéros de carte bancaire sur notre site, toute la partie finance est déléguée aux grands groupes anglophones de gestion de paiement(…).
  • Son code est vraiment spécifique, il n’utilise pas la fonctionnalité native de Filtrage de AngularJs qui est si révolutionnaire et je vois pas comment je vais pouvoir l’intégrer moi même ! Je pense que ce code est peut être  outdated mais je peux me tromper(ce n’est pas connoté négativement), néanmoins il est bien de l’analyser.

Son code, et mes notes:


Le fichier principal unique du répertoire Root : defaut.html :

on voit ici la directive ng-view qui affiche les vues dans la div appropriée.

<!doctype html>
<html ng-app="AngularStore">
  <head>
    <title>Magasin Angular </title>

    <!-- jQuery, Angular -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript" ></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js" type="text/javascript"></script>

    <!-- Bootstrap -->
    <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet" type="text/css"/>
    <!--<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js" type="text/javascript" ></script>-->

    <!-- Stripe -->
    <script src="https://checkout.stripe.com/v2/checkout.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.form/3.32/jquery.form.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.61.0-2013.06.06/jquery.blockUI.min.js"></script>

    <!-- AngularStore app -->
    <script src="js/product.js" type="text/javascript"></script>
    <script src="js/store.js" type="text/javascript"></script>
    <script src="js/shoppingCart.js" type="text/javascript"></script>
    <script src="js/app.js" type="text/javascript"></script>
    <script src="js/controller.js" type="text/javascript"></script>
    <link href="css/style.css" rel="stylesheet" type="text/css"/>
  </head>

  <body>

    <!-- 
        bootstrap fluid layout
        (12 columns: span 10, offset 1 centers the content and adds a margin on each side)
    -->
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span10 offset1">
                <h1 class="well" >
                    <a href="default.htm">
                        <img src="img/logo.png" height="60" width="60" alt="logo"/>
                    </a>
                    Magasin Angular 
                </h1>
                <div ng-view></div>
            </div>
        </div>
    </div>

  </body>
</html>

Le répertoire JS contient donc :

1. Le controleur du magasin controller.js.

On voit qu’il injecte carrément des objets(modèles) complets dans le controlleur, on voit aussi le routing qui affiche les vues des objets dans le fichier principal. Enfin, on voit que le service DataService est injecté dans le controleur AngularJS:

'use strict';

// le controleur du magasin contient 2 objets:
// - store: contient la liste des produits
// - cart: contient la liste du panier
function storeController($scope, $routeParams, DataService) {

    // Obtient le magasin et le panier du service DataService
    $scope.store = DataService.store;
    $scope.cart = DataService.cart;

    // Utilise le routing pour afficher le produit choisi et l'afficher dans la vue.
    if ($routeParams.productSku != null) {
        $scope.product = $scope.store.getProduct($routeParams.productSku);
    }
}

2. Le fichier app.js contient le routing angular qui affiche les différentes vues, et un service Data

qui permet de partager les datas entre les vues et de gérer le paiement:

'use strict';


// Le module de routing d'Angular parse l'url et injecte la vue  à l'endroit approprié dans la page html.
var storeApp = angular.module('AngularStore', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
      when('/store', {
        templateUrl: 'partials/store.htm',
        controller: storeController 
      }).
      when('/products/:productSku', {
        templateUrl: 'partials/product.htm',
        controller: storeController
      }).
      when('/cart', {
        templateUrl: 'partials/shoppingCart.htm',
        controller: storeController
      }).
      otherwise({
        redirectTo: '/store'
      });
}]);

// Le service suivant crée un magasin et un panier partagé par toutes les vues, à la place d'en créer un nouveau pour chaque vue. 

storeApp.factory("DataService", function () {

    // créer le magasin
    var myStore = new store();

    // créer le panier
    var myCart = new shoppingCart("AngularStore");

    // Active le paiement PAYPAL
    // note: Le second paramêtre identifie le marchand; Pour l'utiliser, il faut créer un ecompte marchant sur paypal ici :
    // https://www.paypal.com/webapps/mpp/merchant
    myCart.addCheckoutParameters("PayPal", "paypaluser@youremail.com");

    // Active le paiement Google Wallet
	// note: Le second paramêtre identifie le marchand; Pour l'utiliser, il faut créer un ecompte marchant sur Google Wallet ici :
    // https://developers.google.com/commerce/wallet/digital/training/getting-started/merchant-setup
    myCart.addCheckoutParameters("Google", "xxxxxxx",
        {
            ship_method_name_1: "UPS Next Day Air",
            ship_method_price_1: "20.00",
            ship_method_currency_1: "USD",
            ship_method_name_2: "UPS Ground",
            ship_method_price_2: "15.00",
            ship_method_currency_2: "USD"
        }
    );

    // Active le paiement Stripe
	// note: Le second paramêtre identifie le marchand; Pour l'utiliser, il faut créer un ecompte marchant sur Google Wallet ici :
    // https://manage.stripe.com/register
    myCart.addCheckoutParameters("Stripe", "pk_test_xxxx",
        {
            chargeurl: "https://localhost:1234/processStripe.aspx"
        }
    );

  
	/* Retourne un objet avec le magasin et le panier */
    return {
        store: myStore,
        cart: myCart
    };
});

3. Enfin, 3 fichiers d’objets Javascript (C’est du Javascript POO).

Je constate ici que je ne développe pas du tout comme lui à ce niveau là. Moi je fais tout le CRUD dans un seul modèle JSON( Voir mon application 8 GestEmployes), je trouve cela plus simple, mais je peux me tromper.
Rappel : Voici comment moi je développe :

focntionnementvoulu

Je fait tout dans le modèle, et j’enregistre directement. Et je ne fais pas ça par hasard, j’ai vu cette technique de coding faite par les autres dev angular.

->En effet, pourquoi créer un constructeur javascript, alors qu’un fichier JSON est DEJA un objet modifiable en live avec AngularJs !<-

Bref, continuons avec notre sujet :

Voici le fichier product.js qui contient selon le concepteur une classe product :(Pour moi, c’est un constructeur) :


//----------------------------------------------------------------
// product class
function product(sku, name, description, price, cal, carot, vitc, folate, potassium, fiber) {
    this.sku = sku; // product code (SKU = stock keeping unit)
    this.name = name;
    this.description = description;
    this.price = price;
    this.cal = cal;
    this.nutrients = {
        "Carotènes": carot,
        "Vitamine C": vitc,
        "Folates": folate,
        "Potassium": potassium,
        "Fibres": fiber
    };
}

Voici le fichier store.js qui contient les produits du magasin(La flemme de traduire tout…):

//----------------------------------------------------------------
// Magasin (contient les produits)
//
// NOTE: Infos nutritionnelles http://www.cspinet.org/images/fruitcha.jpg
// Legende du score:
// 0: En dessous de 5% de valeur par jours (DV)
// 1: 5-10% VJ
// 2: 10-20% VJ
// 3: 20-40% VJ
// 4: above 40% VJ
//
function store() {
    this.products = [
        new product("APL", "Pomme", "Eat one every day to keep the doctor away!", 12, 90, 0, 2, 0, 1, 2),
        new product("AVC", "Avocat", "Guacamole anyone?", 16, 90, 0, 1, 1, 1, 2),
        new product("BAN", "Banane", "These are rich in Potassium and easy to peel.", 4, 120, 0, 2, 1, 2, 2),
        new product("CTP", "Cantaloupe", "Delicious and refreshing.", 3, 50, 4, 4, 1, 2, 0),
        new product("FIG", "Figue", "OK, not that nutritious, but sooo good!", 10, 100, 0, 0, 0, 1, 2),
        new product("GRF", "Raisin", "Pink or red, always healthy and delicious.", 11, 50, 4, 4, 1, 1, 1),
        new product("GRP", "Grape", "Wine is great, but grapes are even better.", 8, 100, 0, 3, 0, 1, 1),
        new product("GUA", "Goyave", "Exotic, fragrant, tasty!", 8, 50, 4, 4, 0, 1, 2),
        new product("KIW", "Kiwi", "These come from New Zealand.", 14, 90, 1, 4, 0, 2, 2),
        new product("LYC", "Lychee", "Unusual and highly addictive!", 18, 125, 1, 4, 0, 2, 2),
        new product("MAN", "Mangue", "Messy to eat, but well worth it.", 8, 70, 3, 4, 0, 1, 1),
        new product("ORG", "Orange", "Vitamin C anyone? Go ahead, make some juice.", 9, 70, 1, 4, 2, 1, 2),
        new product("PAP", "Papaye", "Super-popular for breakfast.", 5, 60, 3, 4, 2, 2, 2),
        new product("PCH", "Peche", "Add some cream and enjoy.", 19, 70, 1, 2, 0, 1, 2),
        new product("PER", "Poire", "Delicious fresh, or cooked in red wine, or distilled.", 8, 100, 0, 2, 0, 1, 2),
        new product("PMG", "Pomegranate", "Delicious, healthy, beautiful, and sophisticated!", 19, 110, 0, 2, 0, 2, 0),
        new product("PNP", "Pineapple", "Enjoy it (but don't forget to peel first).", 4, 60, 0, 3, 0, 0, 1),
        new product("PSM", "Persimmon", "Believe it or not, they are berries!", 6, 120, 4, 3, 0, 1, 3),
        new product("STR", "Strawberry", "Beautiful, healthy, and delicious.", 7, 40, 0, 4, 1, 1, 2),
        new product("TNG", "Tangerine", "Easier to peel than oranges!", 8, 50, 3, 4, 1, 1, 2),
        new product("WML", "Watermelon", "Nothing comes close on those hot summer days.", 4, 90, 4, 4, 0, 1, 1)
    ];
    this.dvaCaption = [
        "Negligeable",
        "Bas",
        "Moyen",
        "Bon",
        "Excellent"
    ];
    this.dvaRange = [
        "En dessous 5%",
        "Entre 5 et 10%",
        "Entre 10 et 20%",
        "Entre 20 et 40%",
        "Au dessus 40%"
    ];
}
store.prototype.getProduct = function (sku) {
    for (var i = 0; i &lt; this.products.length; i++) {
        if (this.products[i].sku == sku)
            return this.products[i];
    }
    return null;
}

Et enfin, shoppingCart.js, le fichier qui s’occupe de toute la gestion  du panier… IL y a de drôles de fonctions dedans, et on constate que ce n’est pas du full angular, mais qu’il y a un peu de tout…




//----------------------------------------------------------------
// Panier
//
function shoppingCart(cartName) {
    this.cartName = cartName;
    this.clearCart = false;
    this.checkoutParameters = {};
    this.items = [];

	// Charge les élèments à partir de la mémoire du navigateur, lors de l'initialisation
    this.loadItems();

    // Sauve les élèments dans la mémoire du navigateur 
    var self = this;
    $(window).unload(function () {
        if (self.clearCart) {
            self.clearItems();
        }
        self.saveItems();
        self.clearCart = false;
    });
}

// Charge les élèment à partir de a mémoire du navigateur
shoppingCart.prototype.loadItems = function () {
    var items = localStorage != null ? localStorage[this.cartName + "_items"] : null;
    if (items != null && JSON != null) {
        try {
            var items = JSON.parse(items);
            for (var i = 0; i &lt; items.length; i++) {
                var item = items[i];
                if (item.sku != null && item.name != null && item.price != null && item.quantity != null) {
                    item = new cartItem(item.sku, item.name, item.price, item.quantity);
                    this.items.push(item);
                }
            }
        }
        catch (err) {
            // ignore errors while loading...
        }
    }
}

// Sauve les élèments dans la mémoire du navigateur 
shoppingCart.prototype.saveItems = function () {
    if (localStorage != null && JSON != null) {
        localStorage[this.cartName + "_items"] = JSON.stringify(this.items);
    }
}

// ajoute un produit au panier
shoppingCart.prototype.addItem = function (sku, name, price, quantity) {
    quantity = this.toNumber(quantity);
    if (quantity != 0) {

        // Mets à jour la quantité pour un produit existant
        var found = false;
        for (var i = 0; i &lt; this.items.length && !found; i++) {
            var item = this.items[i];
            if (item.sku == sku) {
                found = true;
                item.quantity = this.toNumber(item.quantity + quantity);
                if (item.quantity &lt;= 0) {
                    this.items.splice(i, 1);
                }
            }
        }

        // Ajouter un nouveau produit
        if (!found) {
            var item = new cartItem(sku, name, price, quantity);
            this.items.push(item);
        }

        // sauvegarder les changements
        this.saveItems();
    }
}

// Prix total pour tous les produits du panier
shoppingCart.prototype.getTotalPrice = function (sku) {
    var total = 0;
    for (var i = 0; i &lt; this.items.length; i++) {
        var item = this.items[i];
        if (sku == null || item.sku == sku) {
            total += this.toNumber(item.quantity * item.price);
        }
    }
    return total;
}

// Prix total pour tous les produits du panier
shoppingCart.prototype.getTotalCount = function (sku) {
    var count = 0;
    for (var i = 0; i &lt; this.items.length; i++) {
        var item = this.items[i];
        if (sku == null || item.sku == sku) {
            count += this.toNumber(item.quantity);
        }
    }
    return count;
}

// Supprimer le panier
shoppingCart.prototype.clearItems = function () {
    this.items = [];
    this.saveItems();
}

// define checkout parameters
shoppingCart.prototype.addCheckoutParameters = function (serviceName, merchantID, options) {

    // check parameters
    if (serviceName != "PayPal" && serviceName != "Google" && serviceName != "Stripe") {
        throw "serviceName must be 'PayPal' or 'Google' or 'Stripe'.";
    }
    if (merchantID == null) {
        throw "A merchantID is required in order to checkout.";
    }

    // save parameters
    this.checkoutParameters[serviceName] = new checkoutParameters(serviceName, merchantID, options);
}

// check out
shoppingCart.prototype.checkout = function (serviceName, clearCart) {

    // select serviceName if we have to
    if (serviceName == null) {
        var p = this.checkoutParameters[Object.keys(this.checkoutParameters)[0]];
        serviceName = p.serviceName;
    }

    // sanity
    if (serviceName == null) {
        throw "Use the 'addCheckoutParameters' method to define at least one checkout service.";
    }

    // go to work
    var parms = this.checkoutParameters[serviceName];
    if (parms == null) {
        throw "Cannot get checkout parameters for '" + serviceName + "'.";
    }
    switch (parms.serviceName) {
        case "PayPal":
            this.checkoutPayPal(parms, clearCart);
            break;
        case "Google":
            this.checkoutGoogle(parms, clearCart);
            break;
        case "Stripe":
            this.checkoutStripe(parms, clearCart);
            break;
        default:
            throw "Unknown checkout service: " + parms.serviceName;
    }
}

// check out using PayPal
// for details see:
// www.paypal.com/cgi-bin/webscr?cmd=p/pdn/howto_checkout-outside
shoppingCart.prototype.checkoutPayPal = function (parms, clearCart) {

    // global data
    var data = {
        cmd: "_cart",
        business: parms.merchantID,
        upload: "1",
        rm: "2",
        charset: "utf-8"
    };

    // item data
    for (var i = 0; i &lt; this.items.length; i++) {
        var item = this.items[i];
        var ctr = i + 1;
        data["item_number_" + ctr] = item.sku;
        data["item_name_" + ctr] = item.name;
        data["quantity_" + ctr] = item.quantity;
        data["amount_" + ctr] = item.price.toFixed(2);
    }

    // build form
    var form = $('&lt;form/&gt;&lt;/form&gt;');
    form.attr("action", "https://www.paypal.com/cgi-bin/webscr");
    form.attr("method", "POST");
    form.attr("style", "display:none;");
    this.addFormFields(form, data);
    this.addFormFields(form, parms.options);
    $("body").append(form);

    // submit form
    this.clearCart = clearCart == null || clearCart;
    form.submit();
    form.remove();
}

// check out using Google Wallet
// for details see:
// developers.google.com/checkout/developer/Google_Checkout_Custom_Cart_How_To_HTML
// developers.google.com/checkout/developer/interactive_demo
shoppingCart.prototype.checkoutGoogle = function (parms, clearCart) {

    // global data
    var data = {};

    // item data
    for (var i = 0; i &lt; this.items.length; i++) {
        var item = this.items[i];
        var ctr = i + 1;
        data["item_name_" + ctr] = item.sku;
        data["item_description_" + ctr] = item.name;
        data["item_price_" + ctr] = item.price.toFixed(2);
        data["item_quantity_" + ctr] = item.quantity;
        data["item_merchant_id_" + ctr] = parms.merchantID;
    }

    // build form
    var form = $('&lt;form/&gt;&lt;/form&gt;');
    // NOTE: in production projects, use the checkout.google url below;
    // for debugging/testing, use the sandbox.google url instead.
    //form.attr("action", "https://checkout.google.com/api/checkout/v2/merchantCheckoutForm/Merchant/" + parms.merchantID);
    form.attr("action", "https://sandbox.google.com/checkout/api/checkout/v2/checkoutForm/Merchant/" + parms.merchantID);
    form.attr("method", "POST");
    form.attr("style", "display:none;");
    this.addFormFields(form, data);
    this.addFormFields(form, parms.options);
    $("body").append(form);

    // submit form
    this.clearCart = clearCart == null || clearCart;
    form.submit();
    form.remove();
}

// check out using Stripe
// for details see:
// https://stripe.com/docs/checkout
shoppingCart.prototype.checkoutStripe = function (parms, clearCart) {

    // global data
    var data = {};

    // item data
    for (var i = 0; i &lt; this.items.length; i++) {
        var item = this.items[i];
        var ctr = i + 1;
        data["item_name_" + ctr] = item.sku;
        data["item_description_" + ctr] = item.name;
        data["item_price_" + ctr] = item.price.toFixed(2);
        data["item_quantity_" + ctr] = item.quantity;
    }

    // build form
    var form = $('.form-stripe');
    form.empty();
    // NOTE: in production projects, you have to handle the post with a few simple calls to the Stripe API.
    // See https://stripe.com/docs/checkout
    // You'll get a POST to the address below w/ a stripeToken.
    // First, you have to initialize the Stripe API w/ your public/private keys.
    // You then call Customer.create() w/ the stripeToken and your email address.
    // Then you call Charge.create() w/ the customer ID from the previous call and your charge amount.
    form.attr("action", parms.options['chargeurl']);
    form.attr("method", "POST");
    form.attr("style", "display:none;");
    this.addFormFields(form, data);
    this.addFormFields(form, parms.options);
    $("body").append(form);

    // ajaxify form
    form.ajaxForm({
        success: function () {
            $.unblockUI();
            alert('Thanks for your order!');
        },
        error: function (result) {
            $.unblockUI();
            alert('Error submitting order: ' + result.statusText);
        }
    });

    var token = function (res) {
        var $input = $('&lt;input type=hidden name=stripeToken /&gt;').val(res.id);

        // show processing message and block UI until form is submitted and returns
        $.blockUI({ message: 'Processing order...' });

        // submit form
        form.append($input).submit();
        this.clearCart = clearCart == null || clearCart;
        form.submit();
    };

    StripeCheckout.open({
        key: parms.merchantID,
        address: false,
        amount: this.getTotalPrice() *100, /** expects an integer **/
        currency: 'usd',
        name: 'Purchase',
        description: 'Description',
        panelLabel: 'Checkout',
        token: token
    });
}

// utility methods
shoppingCart.prototype.addFormFields = function (form, data) {
    if (data != null) {
        $.each(data, function (name, value) {
            if (value != null) {
                var input = $("&lt;input&gt;&lt;/input&gt;").attr("type", "hidden").attr("name", name).val(value);
                form.append(input);
            }
        });
    }
}
shoppingCart.prototype.toNumber = function (value) {
    value = value * 1;
    return isNaN(value) ? 0 : value;
}

//----------------------------------------------------------------
// checkout parameters (one per supported payment service)
//
function checkoutParameters(serviceName, merchantID, options) {
    this.serviceName = serviceName;
    this.merchantID = merchantID;
    this.options = options;
}

//----------------------------------------------------------------
// items in the cart
//
function cartItem(sku, name, price, quantity) {
    this.sku = sku;
    this.name = name;
    this.price = price * 1;
    this.quantity = quantity * 1;
}

Publicités