Skip to main content

Grails et ExtJs, vers du RIA plus simple – Partie 1

Introduction

Depuis quelque temps, je m’intéresse aux frameworks permettant la création d’interfaces web « riches » et plus particulièrement le Framework ExtJs qui est donc l’un des Framework RIA Javascript.

ExtJs est, pour moi, la meilleure librairie de sa catégorie, le plus complet en termes de composants graphiques et le mieux élaboré à mon sens. Certains préfèreraient dojo toolkit ou jQueryUI, notamment à cause de la licence commerciale d’ExtJs s’il est utilisé dans un projet ou une application propriétaire.

Aujourd’hui, dans le cadre du premier billet de cet article à 2 parties, je vais présenter un cas d’utilisation du Framework ExtJs avec le Framework Grails pour implémenter une simple liste de contacts. Dans le cadre de la deuxième partie, j’ajouterai le reste des fonctionnalités CRUD avec la gestion de validation.

Création de l’application Grails exemple

Il s’agit dans cet exemple de créer une application simple de gestion de contact. Commençons donc par créer l’application Grails.

grails create-app grails-extjs-contact

Ajoutons ensuite une classe de domaine Contact avec ses attributs et ses contraintes de validation

grails create-domain-class fr.nadouani.sample.Contact

Voici le code de la classe de domaine créée

package fr.nadouani.sample

class Contact {
	String firstname
	String lastname
	Date birthday
	String email
	String phone
	String address

    static constraints = {
		firstname(nullable:false, blank:false)
		lastname(nullable:false, blank:false)
		birthday(nullable:false)
		email(email:true)
		phone()
		address(maxSize: 256)
    }

	String toString(){
		"$firstname $lastname"
	}
}
Listing 1 : définition de la classe de domaine Contact

Vérifions le fonctionnement de l’application Grails après la génération d’un contrôleur ContactController et des vues correspondantes avec la commande suivante

grails generate-all fr.nadouani.sample.Contact

Lançons l’application et testons les fonctionnalités de base du CRUD généré http://localhost:8080/grails-extjs-contact

grails run-app
Formulaire d'édition de contact
Formulaire d'édition de contact
Liste des contacts
Liste des contacts

Ajout de la librairie ExtJs

Pour ajouter le framework ExtJs dans le projet grails, commençons par télécharger la dernière version disponible (à ce jour c’est la version 3.2.1) à cette adresse http://www.sencha.com/products/js/thank-you.php?dl=extjs321. Une fois l’archive décompressée, copions le contenu de cette archive dans le dossier web-app/js de notre application grails.

Transformation de l’application Grails

Transformation du contrôleur ContactController

La première étape de cette transformation consiste à créer une vue grails-app/contact/index.gsp correspondant à l’action « index » du contrôleur ContactController. Ensuite, il faut enlever la redirection qui se trouve dans cette action du même contrôleur pour pouvoir afficher la vue et le fichier index.gsp

Dans ce dernier fichier, nous importons les fichiers nécessaires pour les librairies ExtJs et le fichier contact.js qui décrit le composant de type Grid responsable d’afficher de la liste des contacts.Voici le contenu du fichier index.gsp

<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
		<title>Contact Grails CRUD</title>

		<script type="text/javascript" src="<g:resource dir='js/ext-3.2.1/adapter/ext' file='ext-base.js' />"></script>
		<script type="text/javascript" src="<g:resource dir='js/ext-3.2.1' file='ext-all.js' />"></script>
		<script type="text/javascript" src="<g:resource dir='js' file='contact.js' />"></script>

		<link rel="stylesheet" type="text/css" href="<g:resource dir='js/ext-3.2.1/resources/css' file='ext-all.css' />" />

		<style type="text/css">
			.silk-contact { background-image: url(../images/silk/user.png) !important; background-repeat: no-repeat; }
		</style>
	</head>
	<body>
		<div class="container" style="margin-left:20px;margin-top:20px;width:600px">
		    <div id="contact-grid"></div>
		</div>
	</body>
</html>
Listing 2 : contenu du fichier index.gsp

Ajout de l’action « read » au controlleur ContactController

Cette action sera utilisée pour retourner la liste des contacts au format JSON attendu par le composant ExtJs. Nous pouvions modifier l’action « list » pour retourner du JSON, mais dans le cadre de cet exemple, nous allons garder le fonctionnement normal de l’action « list » générée par grails

Voici le contenu de l’action « read » dans ContactController

def read = {
	params.max = Math.min(params.limit ? params.int('limit') : 10, 100)
	params.offset = params.start ? params.int('start') : 0
	params.order = params.dir ? params.dir.toLowerCase() : ""

	def dataToRender = ['data' : Contact.list(params), 'totalCount' : Contact.count() ]

	render dataToRender as JSON
}
Listing 3 : code de l’action « read » du contrôleur ContactController

Cette action commence par récupérer les paramètres nécessaires pour la pagination (max et offset) et pour le tri des connées (order). Ensuite, un objet JSON est construit et renvoyé à la DataGrid côté client.

Ajout du fichier contact.js

Ce fichier contient le code javascript permettant la création d’un composant GridPanel, un composant graphique ExtJs permettant d’afficher une data grid.

var proxy = new Ext.data.HttpProxy({
    api: {
        read : 'read',
        create : 'save',
        update: 'save',
        destroy: 'delete'
    }
});

var reader = new Ext.data.JsonReader({
    totalProperty: 'totalCount',
    successProperty: 'success',
    idProperty: 'id',
    root: 'data',
    messageProperty: 'message'
}, [
    {name: 'id'},
    {name: 'firstname', type:'string',  allowBlank: false},
    {name: 'lastname', type:'string', allowBlank: false},
    {name: 'email', type:'string'},
    {name: 'birthday', type:'date', allowBlank: false},
    {name: 'phone', type:'String'},
    {name: 'address', type:'String'}
]);

var writer = new Ext.data.JsonWriter({
    encode: true,
    writeAllFields: false
});

var store = new Ext.data.Store({
    proxy: proxy,
    reader: reader,
    writer: writer,
    autoSave: false,
    remoteSort: true
});

Ext.onReady(function() {
    Ext.QuickTips.init();

    var contactGrid = new Ext.grid.EditorGridPanel({
    	width: 700,
    	height: 300,
    	title: 'Contacts',
    	renderTo: 'contact-grid',
    	iconCls: 'silk-contact',
    	frame: true,
        store: store,
        columns : [
			{header: "ID", width: 40, sortable: true, dataIndex: 'id'},
			{header: "Firstname", width: 100, sortable: true, dataIndex: 'firstname'},
			{header: "Lastname", width: 100, sortable: true, dataIndex: 'lastname'},
			{header: "Email", width: 100, sortable: true, dataIndex: 'email'},
			{header: "Phone", width: 100, sortable: true, dataIndex: 'phone'},
			{header: "Address", width: 100, sortable: true, dataIndex: 'address'}
		],
        viewConfig : {
            forceFit: true
        },
        bbar: new Ext.PagingToolbar({
			pageSize: 5,
			store: this.store,
			displayInfo: true,
			displayMsg: 'Displaying contact {0} - {1} of {2}',
			emptyMsg: "No contact to display"
		})
    });

    store.load({params:{start:0, limit:5}});
});
Listing 4 : contenu du fichier contact.js qui décrit la création de la DataGrid des contacts

A la ligne 1 du listing 4, nous définissons un HttpProxy qui est un objet qui regroupe les urls à appeler pour chaque type de requête :

  • read : l’url de lecture
  • create : l’url de création de nouveau contact
  • update : l’url d’édition de contact existant
  • destroy : l’url de suppression de contact

Dans notre exemple, les urls sont spécifiées en chemin relatif à l’action courante

A la ligne 10 du listing 4, nous définissons un JsonReader qui est l’objet responsable d’interpréter la réponse du serveur. Dans le cas d’une requête de type « read » par exemple, le HttpProxy appelle l’action « read » du contrôleur ContactController qui renvoie le JSON suivant :

{
"data":[
	{"id":1,"address":"","birthday":"1983-04-09T22:00:00Z","email":"[email protected]","firstname":"Nabil","lastname":"Adouani","phone":""},

	{"id":2,"address":"Washington","birthday":"1955-10-27T23:00:00Z","email":"[email protected]","firstname":"Bill","lastname":"Gates","phone":""},

	{"id":3,"address":"Palo Alto, California","birthday":"1955-02-23T23:00:00Z","email":"steve.jobe[email protected]","firstname":"Steven Paul","lastname":"Jobs","phone":""},

	{"id":4,"address":"City of Springfield","birthday":"1986-12-31T23:00:00Z","email":"[email protected]","firstname":"Bart","lastname":"Simpson","phone":""}
	],
"totalCount":4
}

Ce JSON est interpreter donc par le JsonReader qui va créer un Record pour chaque element de la liste « data » en utilisant la définition des champs id, firstname, lastname, birthday, email, phone et address

A la ligne 26 du listing 4, nous définissions un JsonWriter qui sera responsable de construire les requêtes à envoyé au serveur dans le cas d’action d’écriture (create, update, destroy). Dans notre exemple, nous spécifions qu’il faut encoder les paramètres des requêtes et d’envoyer la valeur de tous les champs au serveur à chaque requête d’écriture, et non pas juste les champs modifiés.

A la ligne 31 du listing 4, nous définissons l’objet JsonStore qui sera utilisé par la DataGrid. Le store est la source de données. Remarquons que l’attribute « remoteSort » est à true, ce qui signifie que le le tri sera fait côté serveur

A la ligne 42 du listing 4, la DataGrid est définie avec les colonnes et leur bindings aux champs retournés par les JsonStore via le JsonReader. Le binding des colonnes est spécifié avec la propriété « dataIndex ».

Test de l’interface ExtJs développée

En accédant à l’url http://localhost:8080/grails-extjs-contact/contact/index nous obtenons une page contenant un GridPanel ExtJs listant les contacts et permettant d’effectuer tri des données et la pagination et l’actualisation de la liste. Nous pouvons aussi lire le nombre total des contacts de la base de données.

GridPanel de la liste des contacts
GridPanel de la liste des contacts

Conclusion

Dans ce billet, j’ai essayé de présenter comment se marient facilement les Frameworks Grails et ExtJs. C’est vrai qu’une connaissance de ce dernier est recommandée pour pouvoir bien manipuler cette intégration entre les deux Frameworks. Ceci dit, le code ExtJs reste très lisible et simple à comprendre.

Dans la deuxième partie de cet article, je reviendrai sur l’implémentation des autres actions du CRUD (Ajout, modification et suppression).

Les sources de l’application sont disponibles ici.