AngularJs ========= AngularJs est un framework javascript permettant d'écrire des applications web en mode **SPA** Single Page Application. Ce framework est open source et est développé par Google. Il est fortement orienté MVC (Modèle Vue Controler) voir MVVM (Modèle Vue Vue Modèle). L'essentiel est de savoir qu'une application AngularJS permet de bien séparer le visuel, de l'opérationnel. Nous allons produire une application AngularJS qui va permettre via un navigateur web de gérer une liste de contact client. De plus nous allons voir que nous pouvons réaliser une application **sexy** via Angular Material qui est une suite de composant élaborés par Google pour réaliser des applications AngularJs. Notre application sera aussi internationalisable ... Il existe une autre bibliothèque de composant nommé modern-ui qui est très en vogue. .. figure:: data/angularjs_result.png Seul la gestion des tags et des users sera opérationnelle. La sauvegarde des "users" sera réalisé via un bouton "save". La sauvegarde des "tags" sera réalisé de façon dynamique. Architecture technique ---------------------- Au niveau de l'architecture en plus du basique serveur Web, nous allons rajouter un serveur d'application qui aura pour rôle de gérer les fonctionnalités techniques et métiers. .. figure:: data/angularjs.png Dans un premier temps le serveur web sera un serveur Apache avec une configuration très basique: son seul rôle est de distribué des fichiers - html - css - font - js Le fichier de configuration su site web ressemble à .. code-block:: apache # conf/site-available/test1.conf ServerName mytest.fr ServerAlias *.mytest.fr DocumentRoot "C:/www/test" AllowOverride None Options None Order allow,deny Allow from all DirectoryIndex index.html ErrorDocument 404 /missing.html ErrorLog "C:/www/logs/error.log" LogLevel info Je vous laisse voir la configuration plus précise d'un serveur apache dans la note dédiée. Dév --- Le serveur applicatif ~~~~~~~~~~~~~~~~~~~~~ On l'écrit en python et permettra de founir des données au format **json**. Le serveur utilisera les modules flask et flask-cors .. code-block:: bash pip install flask pip install flask-cors .. note:: Le module flask-cors permet de gérer le problème de Access-Control-Allow-Origin Par défaut un client ne peut faire de demande json que sur le même serveur. Dans notre architecture on sépare bien le serveur web du serveur d'application Le serveur applicatif est donc écrit ainsi .. code-block:: python from flask import Flask from flask import jsonify from flask import json from flask import request from flask.ext.cors import CORS app = Flask(__name__) cors = CORS(app) USERS = {'1': {'name':'nameA', 'forname' : 'fornameA', 'email' : 'nameA@domaine.fr'}, '2': {'name':'nameB', 'forname' : 'fornameB', 'email' : 'nameB@domaine.fr'}, '3': {'name':'nameC', 'forname' : 'fornameC', 'email' : 'nameC@domaine.fr'}, '4': {'name':'nameD', 'forname' : 'fornameD', 'email' : 'nameD@domaine.fr'}, '5': {'name':'nameE', 'forname' : 'fornameE', 'email' : 'nameE@domaine.fr'}, '6': {'name':'nameF', 'forname' : 'fornameF', 'email' : 'nameF@domaine.fr'}, '7': {'name':'nameG', 'forname' : 'fornameG', 'email' : 'nameG@domaine.fr'}, } TAGS = { '1': {'name':'tags A', 'commentary':''}, '2': {'name':'tags B', 'commentary':''}, '3': {'name':'tags C', 'commentary':''}, '4': {'name':'tags D', 'commentary':''}, '5': {'name':'tags E', 'commentary':''}, '6': {'name':'tags F', 'commentary':''}, } @app.errorhandler(404) def not_found(error=None, msg=''): message = { 'status': 404, 'message': 'Not Found: ' + msg, } resp = jsonify(message) resp.status_code = 404 return resp @app.route('/users/', methods = ['GET']) def api_user(userid): if userid in USERS: return json.dumps([USERS[userid]]) else: return not_found(error=None, msg="user %s" % userid) @app.route('/users', methods = ['GET']) def api_users(): return json.dumps([dict(USERS[userid], id=userid) for userid in USERS.keys()]) @app.route('/setuser', methods = ['POST']) def api_setuser(): data = json.loads(request.data.decode()) id = int(data['user']['id']) USERS[id] = data['user'] print('set user %s' % id) return json.dumps([{"msg":"ok"}]) @app.route('/tags', methods = ['GET']) def api_tags(): return json.dumps([dict(USERS[userid], id=userid) for userid in USERS.keys()]) @app.route('/tag', methods = ['POST']) def api_settag(): data = json.loads(request.data.decode()) id = int(data['tag']['id']) TAGS[id] = data['tag'] print('set tag %s' % id) return json.dumps([{"msg":"ok"}]) if __name__ == '__main__': app.run() Notre serveur répond donc au demande: - /users: donne la liste des utilisateurs - /setuser: modifie un utilisateur - /tags: donne la liste des tags - /settag: modifie un tag Le code client ~~~~~~~~~~~~~~ Le code client nécessite les modules: - angularjs - angular material - font-awesome - angular-fontawesome Dans notre code, il y aura une barre d'outil dynamique. Volontairement nous allons créer des **directives** propres pour gérer: - la barre d'outil - la liste des utilisateurs - la liste des tags .. note:: bower peut être l'outil pour installer, utiliser et maintenir ces bibliothèques dans votre projet On utilise des templates pour les utilisateurs et tags: cela permet de gérer de façon indépendante le visuel utilisateurs et tags sans modifier le fichier principal. index.html .. code-block:: html
Toolbar with Icon Buttons
plane
gear
css/mycss.html .. code-block:: css div.menu { padding: 5 5 10 5; font-size: 1.3em; } .fa { padding-right: 5px; } md-toolbar { box-shadow: none !important; } .md-toolbar-tools { padding-left: 5px; } md-sidenav md-list { padding-left: 0px; padding-top: 0px; } md-sidenav.md-locked-open md-list { padding-left: 5px; padding-top: 5px; } .view-current { padding-left: 5px; padding-top: 5px; } label { padding-left: 1px; } app/app.js .. code-block:: js var module = angular.module("MyBookCustomer", ['picardy.fontawesome', 'ngMaterial', 'common', 'user', 'tag']); module.controller('ViewsController' , function ($scope) { $scope.selectView('tag'); }); Ce fichier permet de charger les modules nécessaire à l'application et de sélectionner la première vue. app/common.js .. code-block:: js var modCommon=angular.module('common', []) // add controler modCommon.controller('AppController' , function ($scope, $mdSidenav) { $scope.app = []; $scope.toolbar = null; $scope.openLeftMenu = function() { $mdSidenav('left').toggle(); }; $scope.showNavIcon = function() { return !$mdSidenav('left').isLockedOpen(); }; $scope.selectView = function(view) { $scope.viewCurrent = view; if ($scope.showNavIcon() && $mdSidenav('left').isOpen()) { $scope.openLeftMenu(); }; }; $scope.changeToolbar = function(toolbar) { $scope.toolbar = toolbar; }; }); // add directive modCommon.directive('menuItem', function () { return { restrict: 'E', scope: {}, template: '', replace: true, link: function (scope, element, attrs) { scope.icon = attrs.icon; scope.title = attrs.title; element.removeAttr('icon'); element.removeAttr('title'); } }; }); app/user.js .. code-block:: js var modUser = angular.module('user', []) // add constant modUser.constant("API_USERS", "http://localhost:5000/users"); modUser.constant("API_SETUSER", "http://localhost:5000/setuser"); // add controller modUser.controller('UserController' , function ($scope, $http, $log, API_USERS, API_SETUSER) { $scope.users = null; $http.get(API_USERS). success(function(data, status, headers, config) { $scope.users = data; }). error(function(data, status, headers, config) { alert('error'); }); $scope.edituser = null; $scope.returnListUser = function(item) { $scope.changeToolbar([]); $scope.edituser = null; } $scope.editUser = function(item) { $scope.changeToolbar([{ label: "Back", fn: $scope.returnListUser, icon: "reply", //show: $scope.showSave },{ label: "Save", fn: $scope.setuser, icon: "save", //show: $scope.showSave }]); $scope.edituser = item; $scope.setuser = function() { alert('setuser'); $http.post(API_SETUSER, {'user':$scope.edituser}). success(function(data, status, headers, config) { // none }). error(function(data, status, headers, config) { alert('error'); }); }; } }); // add directive modUser.directive('usersView', function () { return { restrict: 'E', templateUrl: 'app/templates/users.html', replace: true }; }); app/tag.js .. code-block:: js var modTag = angular.module('tag', []) // add constant modTag.constant("API_TAGS", "http://localhost:5000/tags"); modTag.constant("API_SETTAG", "http://localhost:5000/settag"); // add controller modTag.controller('TagController' , function ($scope, $http, $log, API_TAGS, API_SETTAG) { $scope.users = null; $http.get(API_TAGS). success(function(data, status, headers, config) { $scope.tags = data; }). error(function(data, status, headers, config) { alert('error'); }); $scope.edittag = null; $scope.returnListTag = function(item) { $scope.changeToolbar([]); $scope.edittag = null; } $scope.editTag = function(item) { $scope.changeToolbar([{ label: "Back", fn: $scope.returnListTag, icon: "reply", //show: $scope.showSave }]); $scope.edittag = item; $scope.settag = function() { $http.post(API_SETTAG, {'tag':$scope.edittag}). success(function(data, status, headers, config) { // none }). error(function(data, status, headers, config) { alert('error'); }); }; } }); // add directive modTag.directive('tagsView', function () { return { restrict: 'E', templateUrl: 'app/templates/tags.html', replace: true }; }); On peut encore rajouter plusieurs options comme l'internationalisation. Ajouter des boîtes d'information en cas de succès ou d'erreur des appels http .. code-block:: js $mdToast.show( $mdToast.simple() .content('Simple Toast!') .position('bottom right') .hideDelay(3000) ); On peut certainement modifier la gestion de liste ou de la toolbar (en la plaçant sur chaque élement de liste) mais ce projet fournit une première approche et l'intérêt d'avoir un serveur d'application. On peut trouver l'ensemble du projet :download:`ici ` Elaboration d'une application ----------------------------- Il est possible d'automatiser et d'utiliser des bonnes pratiques rendant l'écriture d'application plus simple la création du répertoire de travail et l'installation des librairies va utiliser l'outil *bower* (cf polymer pour installation) .. code-block:: bash mkdir myappli cd myappli echo { > .bowerrc echo "directory": "bower_components/" >> .bowerrc echo } >> .bowerrc bower init bower install angular bower install angular-route bower install angular-material bower install webcomponentsjs bower install angular-fontawesome mkdir styles touch styles/app-theme.html touch styles/app-theme-more.html mkdir app mkdir app/template touch app/app.js touch elements.html touch index.html mkdir images mkdir images/touch touch images/touch/icon_16x16.png touch images/touch/icon_128x128.png touch images/touch/icon_144x144.png touch images/touch/icon_152x152.png touch images/touch/icon_192x192.png touch favicon.ico mkdir server touch server/server.py pip install flask pip install flask-cors Voilà on est prêt pour écrire notre application. Le fichier app-theme.html contient une palette de couleur obtenue sur materialpalette.com au format *polymer*. .. note:: il est possible d'installer en local la doc d'angular material .. code-block:: bash npm install -g gulp git clone https://github.com/angular/material cd material npm install gulp build gulp docs cd dist/docs On peut trouver l'ensemble du projet :download:`ici `