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.

_images/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.

_images/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 à

# conf/site-available/test1.conf
<VirtualHost *:80>
    ServerName mytest.fr
            ServerAlias *.mytest.fr

            DocumentRoot "C:/www/test"
            <Directory "C:/www/test">
                AllowOverride None
                Options None
                Order allow,deny
                Allow from all
            </Directory>

            <IfModule dir_module>
                DirectoryIndex index.html
            </IfModule>
            ErrorDocument 404 /missing.html

            ErrorLog "C:/www/logs/error.log"
            LogLevel info
</VirtualHost>
                                                                                                                                                                                                              </VirtualHost>

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

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

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/<userid>', 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

<html>
    <head>
        <meta charset="UTF-8">
        <!-- Angular Material CSS now available via Google CDN; version 0.8 used here -->
        <link rel="stylesheet" href="scripts/angular_material/0.8.3/angular-material.min.css"/>
        <!-- Angular Material Dependencies -->
        <script src="scripts/angularjs/1.3.15/angular.min.js"></script>
        <script src="scripts/angularjs/1.3.15/angular-animate.min.js"></script>
        <script src="scripts/angularjs/1.3.15/angular-aria.min.js"></script>
        <script src="scripts/angularjs/1.3.15/angular-resource.min.js"></script>
        <!-- Angular Material Javascript now available via Google CDN; version 0.8 used here -->
        <script src="scripts/angular_material/0.8.3/angular-material.min.js"></script>
        <!-- Font Awesome -->
        <link rel="stylesheet" href="scripts/font-awesome/4.3.0/css/font-awesome.min.css">
        <script src="scripts/angular-fontawesome/0.2/angular-fontawesome.js"></script>

        <!-- MyBookCustomer application -->
        <script src="app/constants.js"></script>
        <script src="app/user.js"></script>
        <script src="app/tag.js"></script>
        <script src="app/common.js"></script>
        <script src="app/app.js"></script>
        <link rel="stylesheet" href="css/mycss.css"/>
    </head>
    <body ng-app="MyBookCustomer">
      <div ng-controller="AppController" layout="column" layout-fill>
          <md-toolbar>
              <div class="md-toolbar-tools">
                  <md-button ng-click="openLeftMenu()" ng-show="showNavIcon()"><fa name="navicon" aria-label="nav"></fa></md-button>
                  <span>Toolbar with Icon Buttons</span>
                  <md-button  ng-repeat="tool in toolbar" ng-click="tool.fn()"><fa name="{{tool.icon}}" aria-label="{{tool.label}}"></fa></md-button>
                  <span flex></span>
                  <md-button><fa name="ellipsis-v" aria-label="configuration"></fa></md-button>
              </div>
          </md-toolbar>
    <md-content id="main" layout="row" layout-fill>
        <md-sidenav class="md-sidenav-left" md-component-id="left" md-is-locked-open="$mdMedia('min-width: 960px')" class="md-default-theme" >
      <md-list>
          <data-menu-item icon="user" title="User" ng-click="selectView('user')"></data-menu-item>
          <data-menu-item icon="tags" title="Tags" ng-click="selectView('tag')"></data-menu-item>
          <data-menu-item icon="plane" title="Planes" ng-click="selectView('plane')"></data-menu-item>
          <md-divider></md-divider>
          <data-menu-item icon="gears" title="Gears" ng-click="selectView('gear')"></data-menu-item>
      </md-list>
        </md-sidenav>
        <md-content id="content" layout-fill>
      <div class="animate-switch-container view-current" ng-switch on="viewCurrent" ng-controller="ViewsController">
          <div class="animate-switch" ng-switch-when="user" ng-controller="UserController"><data-users-view></data-users-view></div>
          <div class="animate-switch" ng-switch-when="tag" ng-controller="TagController"><data-tags-view></data-tags-view></div>
          <div class="animate-switch" ng-switch-when="plane">plane</div>
          <div class="animate-switch" ng-switch-when="gear">gear</div>
          <div class="animate-switch" ng-switch-default></div>
      </div>
        </md-content>
    </md-content>
      </div>
    </body>
</html>

css/mycss.html

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

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

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: '<md-list-item tabindex="0" role="listitem"><div class="menu"><i name="{{icon}}" class="fa fa-{{icon}}"></i><span>{{title}}</span></div></md-list-item>',
      replace: true,
      link: function (scope, element, attrs) {
        scope.icon = attrs.icon;
        scope.title = attrs.title;
        element.removeAttr('icon');
        element.removeAttr('title');
      }
    };
  });

app/user.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

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

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

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

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 ici