Utilisation des associations entre les Models dans ExtJs

ExtJs 4 fournie un nouveau modèle de gestion des données basé essentiellement sur les classes Ext.data.Model et Ext.data.Store. Dans cet article, je vais vous montrer comment utiliser la capacité de ExtJs à associer les Model ensemble, de la même façon que l’on définit les relations dans les ORM.

 

1. Données de départ

Le jeu de données va être très simple :  une liste d’auteur et une liste de livre associé.
Dans cet article je vais montrer comment utiliser ExtJs dans un environnement dit « classique ».
Je vais créer une page HTML avec 3 liens : le premier ne chargera que les auteurs, le second que les livres et le dernier les deux associés.

Je me suis basé sur la version 4.0.2a de ExtJs pour cet article.

 

2. Gestion des auteurs

2.a. Le Model

Les bonnes pratiques d’ExtJs est d’utiliser leur modèle objet. Ext.define() permet de définir de nouvelles classes avec de l’héritage et Ext.create() permet de créer un instance d’une classe.

Le Model dans Extjs permet de décrire un enregistrement (Record). Il doit définir au minimum les champs et leurs types. Dans les bonnes pratiques de Extjs, il est également conseillé d’y définir le proxy, qui pourra toujours être surchargé dans le Store si besoin.
Dans un fichier Author.js je définis mon Model comme suit :

Ext.define('Author', {
    extend: 'Ext.data.Model',
    fields: [
              {name: 'ID', type: 'string'},
              {name: 'NAME', type: 'string'}
    ],
 
    proxy: {
        type: 'ajax',
        url: '/data/authors.json',
        reader: {
        	type: 'json',
            root: 'AUTHORS',
            totalProperty: 'totalCount'}
	}
});
 

 

2.b. Le Store

Le Store dans ExtJs est un conteneur d’enregistrement (Record). C’est cet objet que l’on manipulera pour charger les données depuis le serveur mais aussi pour accéder aux différents enregistrements.
Dans un fichier Authors.js je définis mon Store comme suit :

Ext.define('Authors', {
    extend: 'Ext.data.Store',
    model: 'Author'
});
 

 

2.c. Les données

Dans le Proxy du Model j’ai spécifié l’adresse d’un fichier plat contenant mes données au format json, en accord avec la configuration du Reader. Voici le contenu de ce fichier :

{
	"success": true,
	"totalCount": 2,
	"AUTHORS": [
		{ "ID": 1, "NAME": "Stephen King" }, 
		{ "ID": 2, "NAME": "J.K. Rowling" }
	]
}
 

 

2.d. Utilisation du Model et du Store

Je définis dans un autre fichier javascript une fonction qui charge les données du fichier authors.json dans le Store et parcoure ce Store pour afficher son contenu dans une fenêtre.
Le chargement des données étant asynchrone, je place le code parcourant les données dans une fonction callback afin d’être sûr que le code ne s’exécute pas avant que les données soient disponibles.

function showAuthor()
{
	var Authors = Ext.create('Authors'), //instanciation du store Authors 
		html = '';
 
	//chargement des données dans le store
	Authors.load({callback: function(){
		Authors.each(function(Author){
			html += '<p>';
			html += 'ID: '+Author.get('ID')+'<br/>';
			html += 'NAME: '+Author.get('NAME');
			html += '</p>';
		});
 
		//affichage du résultat
		Ext.create('Ext.window.Window', {
	        title: 'Authors',
	        height: 400,
	        width: 600,
	            html: html,
	            autoScroll: true
	    }).show();
	}});
}
 

Il ne reste plus qu’a créé une page HTML pour charger les fichiers et appelé la fonction showAuthor().

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
	<title>Nested</title>
	<link rel="stylesheet" type="text/css" href="ext-4.0.2a/resources/css/ext-all.css">
 
    <script type="text/javascript" src="ext-4.0.2a/ext-all-debug.js"></script>
 
 
</head>
<body>
 
<p>
<a href="#" onclick="showAuthor(); return false;">Voir les Auteurs</a>
</p>
 
 
<script type="text/javascript" src="nested_models/Author.js"></script>
<script type="text/javascript" src="nested_models/Authors.js"></script>
<script type="text/javascript" src="nested_models/app.js"></script>
 
</body>
</html>
 

En cliquant sur le lien « Voir les Auteurs » vous devriez obtenir le résultat suivant.
Fenêtre ExtJS affichant les auteurs de livre

 

3. Gestion des livres

J’utilise le même principe de développement pour les livres.

 

3.a. Le Model

Ext.define('Book', {
    extend: 'Ext.data.Model',
    fields: [
              {name: 'ID', type: 'string'},
              {name: 'TITLE', type: 'string'}
    ],
 
 
    proxy: {
        type: 'ajax',
        url: '/data/books.json',
        reader: {
        	type: 'json',
            root: 'BOOKS',
            totalProperty: 'totalCount'
    	}
	}
});
 

 

3.b. Le Store

Ext.define('Books', {
    extend: 'Ext.data.Store',
    model: 'Book'
});
 

 

3.c. Les données

{
	"success": true,
	"totalCount": 3,
	"BOOKS": [
		{ "ID": 1, "TITLE": "It" },
		{ "ID": 2, "TITLE": "Charlie" },
		{ "ID": 3, "TITLE": "Dreamcatcher" }
	]
}
 

 

3.d. Utilisation du Model et du Store

La fonction javascript qui s’occuper de charger les données et de les parcourir.

function showBook()
{
	var Books = Ext.create('Books'), //instanciation du store Books 
		html = '';
 
	//chargement des données dans le store
	Books.load({callback: function(){
		//parcours des données
		Books.each(function(Book){
			html += '<p>';
			html += 'ID: '+Book.get('ID')+'<br/>';
			html += 'TITLE: '+Book.get('TITLE');
			html += '</p>';
		});
 
		//affichage du résultat
		Ext.create('Ext.window.Window', {
	        title: 'Books',
	        height: 400,
	        width: 600,
	            html: html,
	            autoScroll: true
	    }).show();
	}});	
}
 

Mise à jour du fichier HTML :

<body>
 
<p>
<a href="#" onclick="showBook(); return false;">Voir les livres</a>
</p>
 
<p>
<a href="#" onclick="showAuthor(); return false;">Voir les Auteurs</a>
</p>
 
<script type="text/javascript" src="nested_models/Author.js"></script>
<script type="text/javascript" src="nested_models/Book.js"></script>
<script type="text/javascript" src="nested_models/Authors.js"></script>
<script type="text/javascript" src="nested_models/Books.js"></script>
<script type="text/javascript" src="nested_models/app.js"></script>
 
</body>
 

En cliquant sur le lien « Voir les livres » vous devriez obtenir le résultat suivant.
fenêtre ExtJS affichant le titre des livres

 

4. Relation entre les Models (Nested data)

ExtJs dans sa version 4 supporte les relations entre les Models et permet de « peupler » des stores à partir d’un seul appel aux données ce qui limite le nombre d’appel au serveur et réduit l’association des données qui du coup n’as plus besoin d’être codé en javascript.

 

4.a. Les Models

Un peu à l’image de Doctrine2, même si je suis sûr qu’il n’y a aucun lien entre les deux,  les relations doivent être définies dans le sens que l’on souhaite les utiliser. Dans mon cas je veux retrouver tous les livres d’un auteur.
Pour cela je dois modifier le Model Author pour lui indiquer la liaison.

Ext.define('Author', {
    extend: 'Ext.data.Model',
    fields: [
              {name: 'ID', type: 'string'},
              {name: 'NAME', type: 'string'}
    ],
 
    hasMany: {model: 'Book', name: 'books'},
 
    proxy: {
        type: 'ajax',
        url: '/data/authors.json',
        reader: {
        	type: 'json',
            root: 'AUTHORS',
            totalProperty: 'totalCount'
    	}
	}
});
 

J’ai ajouté la ligne hasMany : …. où model référence le Model Book et name représentes mon futur accesseurs.

Si j’avais besoin de retrouver l’auteur depuis un livre, je devrai définir une relation belongsTo dans le Model Book.

 

4.b. Le Store

Je crée un nouveau Store qui va étendre le Store Authors, car j’ai besoin de surcharger le proxy pour charger un autre fichier de donnée json.

Ext.define('NestAuthors', {
    extend: 'Authors', 
 
    proxy: {
        type: 'ajax',
        url: '/data/nest_authors.json',
        reader: {
        	type: 'json',
            root: 'AUTHORS',
            totalProperty: 'totalCount'
    	}
	}
});
 

 

4.c. Les données

Mon nouveau fichier de données ressemble à ça :

{
	"success": true,
	"totalCount": 2,
	"AUTHORS": [
		{ 
			"ID": 1, 
			"NAME": "Stephen King",
			"books": [
				{ "ID": 1, "TITLE": "It" },
				{ "ID": 2, "TITLE": "Charlie" },
				{ "ID": 3, "TITLE": "Dreamcatcher" }
			] 
		},{ 
			"ID": 2, 
			"NAME": "J.K. Rowling",
			"books": [
				{ "ID": 1, "TITLE": "Harry Potter Vol. 1" },
				{ "ID": 2, "TITLE": "Harry Potter Vol. 2" },
				{ "ID": 3, "TITLE": "Harry Potter Vol. 3" },
				{ "ID": 4, "TITLE": "Harry Potter Vol. 4" }
			] 
		}
	]
}
 

Pour chaque auteur je définis une liste de livre. Notez que j’utilise « books » que j’ai définis pour nommer la relation dans le Model.

 

4.d. Utilisation du Model et du Store

L’utilisation ressemble beaucoup à celles faites précédemment.

function showNested()
{
	var NestAuthors = Ext.create('NestAuthors'), 
		html = '';
 
	NestAuthors.load({callback: function(){
		NestAuthors.each(function(Author){
			html += '<p>';
			html += 'ID: '+Author.get('ID')+'<br/>';
			html += 'NAME: '+Author.get('NAME')+'<br/>';
 
			html += 'Books: '+'<br/>';
			html += '<ul>';
 
			Author.books().each(function(Book){
				html += '<li>TITLE: '+Book.get('TITLE')+'</li>';
			});
 
			html += '</ul>';
			html += '</p>';
		});
 
		Ext.create('Ext.window.Window', {
	        title: 'Authors & Books',
	        height: 400,
	        width: 600,
	            html: html,
	            autoScroll: true
	    }).show();
	}});
}
 

Notez que pour chaque Author ExtJs construit un accesseur basé sur le paramètre « name » définis dans la relation. Cet accesseur ne retourne pas le Model Book mais le Store Books peuplé par les données du fichier json.

Mise à jour du fichier HTML :

<body>
 
<p>
<a href="#" onclick="showBook(); return false;">Voir les livres</a>
</p>
 
<p>
<a href="#" onclick="showAuthor(); return false;">Voir les Auteurs</a>
</p>
 
<p>
<a href="#" onclick="showNested(); return false;">Voir le nested</a>
</p>
 
<script type="text/javascript" src="nested_models/Author.js"></script>
<script type="text/javascript" src="nested_models/Book.js"></script>
<script type="text/javascript" src="nested_models/Authors.js"></script>
<script type="text/javascript" src="nested_models/Books.js"></script>
<script type="text/javascript" src="nested_models/NestAuthors.js"></script>
<script type="text/javascript" src="nested_models/app.js"></script>
 
</body>
 

En cliquant sur le lien « Voir le nested » vous devriez obtenir le résultat suivant.
fenêtre ExtJS affichant les Id, auteur et titre de livres

 

Finalement les relations entre les Models sont assez simples à mettre en œuvre avec ExtJs et permettent de gagner en tant de développement. N’hésitez pas à les utiliser même si vous n’en avez pas besoin dans toutes vos pages. ExtJs associe les données si elles sont définies dans les retours json et n’envoie aucune erreur s’il n’y a pas de données imbriquées.

Vous pouvez télécharger les sources de l’exemple ici. Attention la librairie ExtJs n’est pas incluse.

Je vous propose aussi cet article sur l'utilisation des Models, Stores et des relations entre les model dans le context MVC de ExtJs.

Il y a 2 commentaires.

Ecrit par Sarah le 9 nov. 2012

Je ne trouve pas ça optimisé. On doit stocker le livre en entier (ID + Titre) dans le store sous l'auteur. N'y-a-t-il pas un moyen de faire en sorte de ne stocker seulement l'id et récupérer les informations quand on en a besoin ? Sinon le tuto m'a aidé à comprendre les associations. Merci

Réponse de Ulrich le 9 nov. 2012

Il n'est pas nécessaire de retourner tous les champs d'un model. Vous pouvez donc ne retourner que les ID pour alléger les données stockées. Il vous faudrat dans ce cas gérer à part le chargement des données complémentaires de votre model au moment de la demande.

Ajouter un commentaire