Wiki

Clone wiki

odelia-gina-angular / Tutoriel

Partie serveur

1. Créer l'application odelia-gina-angular

Créez l'application odelia-gina-angular avec la commande Grails create-app, en précisant le profil Grails angular :

grails create-app odelia-gina-angular --profile=angular

Placez-vous ensuite dans le répertoire de l'application ainsi créée.

2. Créer la ressource et classe métier ToDo

Créez ensuite la ressource ToDo au moyen de la commande Grails create-domain-resource :

grails create-domain-resource ToDo

Cette commande créera en particulier le fichier source ToDo.groovy dans le répertoire grails-app/domain/odelia/gina/angular, contenant la définition de la classe métier persistante ToDo déclarée comme ressource web, grâce à l'annotation @Resource placée sur la classe.

Complétez cette classe, de sorte qu'au final, vous ayez le code ci-dessous :

package odelia.gina.angular

import grails.rest.Resource

@Resource(uri='/todos', formats=['json'])
class ToDo {
    String subject
    Date dateCreated
    Date lastUpdated
    Boolean completed

    static constraints = {
    }
}

Notez l'utilisation de l'URI /todos dans le paramètre uri de l'annotation @Resource, ainsi que la restriction du format de réponse à JSON (par défaut, les formats de réponse sont XML et JSON).

L'annotation @Resource permet la génération d'un contrôleur, toDo, qui apparaîtra dans la liste des contrôleurs disponibles dans la page d'accueil de l'application.

Exécutez la commande Grails url-mappings-report :

grails url-mappings-report

La commande ci-dessous permet en particulier de lister les correspondances entre les URI, les verbes HTTP, et les actions du contrôleur Grails toDo qui seront déclenchées :

Controller: toDo
 |   GET    | /todos/create                                     | Action: create           |
 |   GET    | /todos/${id}/edit                                 | Action: edit             |
 |   POST   | /todos                                            | Action: save             |
 |   GET    | /todos                                            | Action: index            |
 |  DELETE  | /todos/${id}                                      | Action: delete           |
 |  PATCH   | /todos/${id}                                      | Action: patch            |
 |   PUT    | /todos/${id}                                      | Action: update           |
 |   GET    | /todos/${id}                                      | Action: show             |

3. Créer des données de test

Modifiez la classe Groovy Bootstrap, du fichier Bootstrap.groovy, qui est dans le répertoire grails-app/init, afin de créer des entités de type ToDo en mode développement :

import odelia.gina.angular.ToDo

class BootStrap {

    def init = { servletContext ->
        environments {
            development {
                5.times {
                    new ToDo(subject: "ToDo #${it+1}", completed: false).save()
                }
            }
        }
    }

    def destroy = {
    }
}

4. Personnaliser le rendu JSON des entités ToDo

Nous allons personnaliser le rendu JSON des entités de type ToDo afin d'exclure l'une de ses propriétés.

Pour cela, ajoutez le bean Spring toDoRenderer dans le fichier resources.groovy du répertoire grails-app/conf/spring :

import grails.rest.render.json.JsonRenderer
import odelia.gina.angular.ToDo

// Place your Spring DSL code here
beans = {
    toDoRenderer(JsonRenderer, ToDo) {
        excludes = ['class']
    }
}

Ici, nous excluons la propriété class du rendu JSON, de sorte qu'elle n'apparaîtra pas avec sa valeur dans la représentation JSON d'une entité de type ToDo.

Notez que toDoRenderer est le nom du bean et n'a pas ici d'importance.

5. Exécuter l'application

Vérifiez qu'à ce stade l'application s'exécute sans problème, avec la commande run-app :

grails run-app

running-app.png

La page d'accueil de l'application doit lister le contrôleur odelia.gina.angular.ToDoController, tandis que naviguer vers l'URL http://localhost:8080/todos, liste les instances ToDo créées dans la classe Bootstrap, au format JSON.

Ensuite, stoppez l'application en exécutant CTRL+C, ou la commande Grails stop-app (dans une autre invite de commandes, ou dans la même si vous êtes en mode interactif).

Partie cliente

Le profil Grails angular utilisé dans ce projet vient avec ses propres commandes dont certaines vont être utiles pour générer le code JavaScript.

1. Ajouter les dépendances vers Bootstrap, xeditable et ng-notify

Grâce au plugin Bower Installer Gradle Plugin configuré dans le script de build Gradle, nous allons pouvoir ajouter les dépendances vers les librairies Bootstrap, xeditable et ng-notify.

Modifier le fichier build.gradle pour y ajouter ce qui suit dans bower {}, et aussi retirer ou mettre en commentaires la dépendance bootstrap qui est en place :

bower {
    // ...
    /*
    'bootstrap'('3.x.x') {
        source 'dist/css/bootstrap.css' >> '/bootstrap/'
    }
    */
    'bootstrap'('3.3.6') {
        source 'dist/css/bootstrap.min.css' >> '/bootstrap/css/'
        source 'dist/css/bootstrap-theme.min.css' >> '/bootstrap/css/'
        source 'dist/js/bootstrap.min.js' >> '/bootstrap/js/'
        source 'dist/fonts/*' >> '/bootstrap/fonts/'
    }
    'angular-xeditable'('0.1.8')
    'ng-notify'('0.7.1')
}

Notez que la dépendance vers AngularJS est déjà présente.

Etant donné que l'on change l'endroit et le nom du fichier .css de Bootstrap, il est nécessaire d'apporter une modification au fichier de style grails-app/assets/stylesheets/application.css utilisé par la page d'accueil :
remplacer la dépendance *= require /bootstrap/bootstrap par *= require /bootstrap/css/bootstrap.min.css.

Exécutez ensuite la commande Gradle bowerRefresh afin de récupérer les nouvelles dépendances dans le projet :

gradle bowerRefresh

A la suite de cette commande de nouveaux répertoires, comme angular-xeditable et ng-notifty apparaîtront dans le répertoire grails-app/assets/bower.

2. Créer le service ToDo

Utilisez la commande Grails create-ng-domain, qui va nous permettre de créer une représentation JavaScript de la ressource web ToDo de la partie serveur :

grails create-ng-domain ToDo

Cette commande crée en particulier le fichier JavaScript ToDo.js dans le répertoire grails-app/assets/javascripts/odelia/gina/angular/domain.

Dans le fichier ToDo.js, modifiez la fonction ToDo pour appeler le service DomainServiceFactory avec de nouveaux arguments (notamment, toDo/:id est remplacé par todos/:id, et on passe un second argument) :

function ToDo(DomainServiceFactory) {
    return DomainServiceFactory("todos/:id", { id: '@id' });
}

Le service DomainServiceFactory qui fait partie du module AngularJS odelia.gina.angular.core (défini par Grails dans le fichier grails-app/assets/javascripts/odelia/gina/angular/core/services/DomainServiceFactory.js), utilise le service $resource du module AngularJS ngResource pour renvoyer un objet capable d'entrer en interaction avec la ressource serveur ToDo. D'où l'utilisation de l'URI todos/... afin d'interroger la ressource ToDo de la partie serveur.
A l'exécution du code JavaScript, :id sera remplacé par la valeur de la propriété id de l'objet ToDo, comme stipulé dans l'objet { id: '@id' } passé en paramètre.

3. Créer le contrôleur AngularJS

Exécutez la commande suivante, afin de créer le contrôleur AngularJS pour la gestion de nos tâches :

grails create-ng-controller todos

Le contrôleur est créé dans le fichier grails-app/assets/javascripts/odelia/gina/angular/controllers/todosController.js. Complétez-le afin d'obtenir le code complet suivant :

//= wrapped

angular
    .module("odelia.gina.angular")
    .controller("TodosController", TodosController);

function TodosController(ToDo, ngNotify) {
    var vm = this;

    vm.errorCallback = function(response) {
        var message = `Error ${response.status} (${response.statusText})`;
        console.error(message);
        ngNotify.set(message, 'error');
    }


    vm.list = function() {
        ToDo.list(
            function(todos) {
                vm.todos = todos;
            },
            vm.errorCallback
        );
    }

    vm.addToDo = function() {
        new ToDo({subject : vm.newSubject, completed: false}).$save(
            function(response) {
                ngNotify.set('New ToDo added!');
                vm.list();
            },
            vm.errorCallback
        );
    }

    vm.updateSubject = function(toDo, subject) {
        toDo.subject = subject;
        vm.update(toDo);
    }

    vm.update = function(toDo) {
        toDo.$update(
            function(response) {
                vm.list();
            },
            vm.errorCallback
        );
    }

    vm.delete = function(toDo) {
        toDo.$delete(
            function(response) {
                vm.list();
            },
            vm.errorCallback
        );
    }

    vm.list();
}

Ce code définit le contrôleur TodosController dans le module AngularJS odelia.gina.angular et bien sûr, fera le lien entre la vue HTML et la partie serveur en se servant du service injecté ToDo (défini dans le module odelia.gina.angular par un appel à factory("ToDo", ToDo) dans le fichier ToDo.js).

Notez l'utilisation des méthodes CRUD $save, $update, $delete, ainsi que de list, sur la référence ToDo ; elles réalisent les interactions avec le service RESTful de la partie serveur.

4. Modifier le module AngularJS

Modifiez le module AngularJS odelia.gina.angular défini dans grails-app/assets/javascripts/odelia/gina/angular/odelia.gina.angular.js

Complétez le code source pour ajouter des dépendances :

  • aux librairies JavaScript jQuery, xeditable, ng-notify et Bootstrap (les directives //= require sont interprétées par le plugin Asset Pipeline) ;
  • aux sources présents dans les répertoires domain et controllers (directives //= require_tree domain et //= require_tree controllers) ;
  • aux autres modules AngularJS (xeditable, ngNotify), dont notre module va dépendre.

odelia.gina.angular.js se présente alors ainsi :

//= wrapped
//= require /jquery/jquery
//= require /angular/angular
//= require /angular-xeditable/xeditable
//= require /ng-notify/ng-notify
//= require /bootstrap/js/bootstrap.min
//= require /odelia/gina/angular/core/odelia.gina.angular.core
//= require /odelia/gina/angular/index/odelia.gina.angular.index
//= require_self
//= require_tree domain
//= require_tree controllers

angular.module("odelia.gina.angular", [
    "odelia.gina.angular.core",
    "odelia.gina.angular.index",
    "xeditable",
    "ngNotify"
])
.run(function(editableOptions) {
    editableOptions.theme = 'bs3';
});

Note : la fonction qui s'exécutera lors de l'appel à run permettra d'appliquer un thème Bootstrap pour les boutons utilisés par la librairie xeditable.

5. Créer le fichier todos.css

Ajoutez un nouveau fichier nommé todos.cssdans le répertoire grails-app/assets/stylesheets avec le contenu suivant :

/*
 *= require /bootstrap/css/bootstrap.min
 *= require /bootstrap/css/bootstrap-theme.min
 *= require /angular-xeditable/xeditable
 *= require /ng-notify/ng-notify
 *= require_self
 */

body {
  min-height: 2000px;
  padding-top: 70px;
}

/* TodoItem text for completed items and not completed items */
.done-false {
  white-space: nowrap;
}

.done-true {
  text-decoration: line-through;
  color: grey;
  white-space: nowrap;
}

.table {
   width: 750px;
}

Notez la gestion des dépendances avec les autres fichiers de style .css avec les directives *= require ( interprétées par le plugin Grails Asset Pipeline).

6. Créer la vue Grails myToDos.gsp

Créez la vue myToDos.gsp dans le répertoire grails-app/views aux côtés de index.gsp :

<!doctype html>
<html>
<head>
    <title>My ToDos list</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <asset:stylesheet src="todos.css"/>
    <asset:link rel="icon" href="favicon.ico" type="image/x-ico" />
    <script type="text/javascript">
        window.contextPath = "${request.contextPath}";
    </script>
</head>
<body ng-app="odelia.gina.angular" ng-controller="TodosController as main">
    <!-- Fixed navbar -->
    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">My ToDos</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <form class="navbar-form navbar-right" ng-submit="main.addToDo()">
            <div class="form-group">
              <input type="text" placeholder="New ToDo" class="form-control" ng-model="main.newSubject" required>
            </div>
            <button type="submit" class="btn btn-success">Add</button>
          </form>
        </div><!--/.navbar-collapse -->
      </div>
    </nav>

    <div class="container">
        <div class="row">
            <div class="span8">
                <h1>My ToDos</h1>
                <p>Shows what I have to do.</p>
            </div>
        </div>
        <div class="row">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th>Id</th>
                        <th>Subject</th>
                        <th>Action</th>
                    </tr>
                </thead>
                <tbody>
                    <tr ng-repeat="todoItem in main.todos track by $index">
                        <th class="col-md-1" scope="row" ng-bind="todoItem.id"></th>
                        <td class="col-md-5">
                            <input type="checkbox" ng-model="todoItem.completed" ng-click="main.update(todoItem)">
                            <a href="#" class="done-{{todoItem.completed}}" ng-bind="todoItem.subject" editable-text="todoItem.subject"
                                onbeforesave="main.updateSubject(todoItem, $data)"></a>
                        </td>
                        <td class="col-md-2">
                            <button type="button" class="btn btn-link" ng-click="main.delete(todoItem)">Delete</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
    <asset:javascript src="odelia/gina/angular/odelia.gina.angular.js" />
</body>
</html>

Notez l'utilisation des tags <asset:stylesheet src="todos.css"/> (dans le tag HTML head) et <asset:javascript src="odelia/gina/angular/todos.js" /> provenant du plugin Asset Pipeline et permettant d'inclure tous les fichiers .css et .js requis.
D'autre part, dans le tag body on définit l'utilisation du module AngularJS odelia.gina.angular, ainsi que celle du contrôleur TodosController, au travers des directives AngularJS ngApp et ngController (ng-app="odelia.gina.angular" ng-controller="TodosController as main").

6. Modifier le fichier UrlMappings.groovy

Modifier le fichier UrlMappings.groovy du répertoire grails-app/controllers/odelia/gina/angular, pour y ajouter la nouvelle correspondance qui suit (par exemple, juste après "/"(view: 'index')), afin de permettre un accès à la page myToDos.gsp :

"/myToDos"(view: '/myToDos')

7. Tester l'application

Démarrez l'application (commande Grails run-app), puis naviguez vers l'URL http://localhost:8080/myToDos.

odelia-gina-angular.png

Si tout se passe bien, vous devriez maintenant pouvoir gérer vos ToDos !

Updated