Utilisation des Models, Store et des relations dans le modèle MVC proposé par ExtJs4

Dans l’article précédent, j’ai montré comment utiliser les Model et les Store de ExtJs et mettre en place des relations entre les Models. Le fonctionnement est identique dans le modèle MVC que propose ExtJS dans sa version 4 mais la déclaration des éléments diffère un peu dû à la structure imposée par le modèle MVC.
Pour cet article je vais me baser sur l’article précédent : l’utilisation des associations entre les Models dans ExtJs, que je vous invite à lire.

 

1. Structure MVC

ExtJs propose un modèle MVC basé sur les classes Ext.application et Ext.app.Controller. Même si la structure des dossiers peut être personnalisée, je préfère me baser sur leurs recommandations.
Je crée dossier appelé « nested_models_mvc » qui contiendra tous mes fichiers javascript. A l’intérieur je crée les dossiers « controller », « model », « store » et « view » pour organiser les fichiers js.

 

2. Création de l’application

Toute application respectant le model MVC dans ExtJs doit contenir un fichier d’entrée, une sorte de front controller qui définis le nom de l’application, son dossier et la liste des objets que l’application utilise : controller, model, store….
Je crée le fichier app.js à la racine du dossier nested_models_mvc .

 

 

Ext.application({
    name: 'NestedMVC',
 
    autoCreateViewport: true,
 
    appFolder: '/nested_models_mvc',
 
    models: [],
    stores: [],
    controllers: [],
 
    launch: function() {
    }
});
 

Je complèterai le fichier plus tard.

 

3. Création de la vue

J’ai activé la création automatique d’un viewport, ce qui implique que ExtJs va chercher un fichier viewport.js dans le dossier view. Dans ce fichier je dois absolument étendre Ext.container.Viewport.

Ext.define('NestedMVC.view.Viewport', {
	extend: 'Ext.container.Viewport',
	layout: 'border',
 
	initComponent: function()
	{
		this.items = [{
			region: 'center',
			xtype: 'panel',
			tbar:[{
				xtype: 'button',
				text: 'Author',
				id: 'authorButton'
			}, {
				xtype: 'button',
				text: 'Book',
				id: 'bookButton'
			}, {
				xtype: 'button',
				text: 'NestAuthor',
				id: 'nestauthorButton'
			}]
		}];
 
		this.callParent();
	}
 
});
 

Notez le nom de la classe que je viens de créer. Dans le modèle MVC, il est important de respecter une convention de nommage. Toutes les classes doivent commencer par le nom de l’application, suivis par le chemin des dossiers puis par son nom.

Ici j’ai créé un viewport dans lequel j’ai ajouté un panel qui contient une barre d’outils avec 3 boutons. La déclaration à la volé des éléments n’est pas idéal dans le modèle MVC mais pour cet exemple on s’en accommodera.

 

4. Création des Models

Les 2 Models dont j’ai besoin sont identique à ceux de l’article précédent. Il faut juste modifier le nom des classes pour respecter la convention de nommage.

Le Model Author: /nested_models_mvc/model/Author.js

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

Le Model Book: /nested_models_mvc/model/Author.js

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

Notez que j’ai aussi modifié le nom des classes dans les attributs hasMany et belongsTo.

 

5. Création des Stores

Comme pour les Models, les stores sont quasi identiques à ceux de l’article précédent à l’exception de la ligne requires qui est obligatoire. Notez également que j’utilise la définition complète des Models et pas seulement le nom de la class.

Le Store Authors : /nested_models_mvc/store/Authors.js

Ext.define('NestedMVC.store.Authors', {
	requires: ['NestedMVC.model.Author'],
    extend: 'Ext.data.Store',
    model: 'NestedMVC.model.Author'
});
 

Le Store Books : /nested_models_mvc/store/Books.js

Ext.define('NestedMVC.store.Books', {
	requires: ['NestedMVC.model.Book'],
    extend: 'Ext.data.Store',
    model: 'NestedMVC.model.Book'
});
 

Le Store imbriqué : /nested_models_mvc/store/NestAuthors.js

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

 

6. Création du Controller

Le controller dans le modèle MVC de ExtJS joue un peu le role du chef d’orchestre. En général on crée un controller par vue. Il est chargé de définir les évènements à écouter dans la vue et de lancer les actions correspondantes.

/nested_models_mvc/controller/PageControl.js

Ext.define('NestedMVC.controller.PageControl', {
	extend: 'Ext.app.Controller',
 
	requires: ['NestedMVC.view.Window'],
	stores: ['Authors', 'Books', 'NestAuthors'],
 
	init: function()
	{
		var me = this;
	    this.control({
	    	'#authorButton': {
	    		click: me.showAuthor
	    	},
	    	'#bookButton': {
	    		click: me.showBook
	    	},
	    	'#nestauthorButton': {
	    		click: me.showNested
	    	}
	    });
	},
 
	onLaunch: function()
	{
	}, 
 
	showBook: function()
	{
		var me = this, 
			html = '';
 
		this.getBooksStore().load({callback: function(){
			this.getBooksStore().each(function(Book){
				html += '<p>';
				html += 'ID: '+Book.get('ID')+'<br/>';
				html += 'TITLE: '+Book.get('TITLE');
				html += '</p>';
			}, this);
 
			Ext.create('NestedMVC.view.Window', {
		        title: 'Books',
	            html: html
		    }).show();
		}, scope: this});
	}, 
 
	showAuthor: function()
	{
		var me = this, 
			html = '';
 
		this.getAuthorsStore().load({callback: function(){
			this.getAuthorsStore().each(function(Author){
				html += '<p>';
				html += 'ID: '+Author.get('ID')+'<br/>';
				html += 'NAME: '+Author.get('NAME');
				html += '</p>';
			}, this);
 
			Ext.create('NestedMVC.view.Window', {
		        title: 'Authors',
	            html: html
		    }).show();
		}, scope: this});
	}, 
 
	showNested: function()
	{
		var me = this, 
			html = '';
 
		this.getNestAuthorsStore().load({callback: function(){
			this.getNestAuthorsStore().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>';
				}, this);
 
				html += '</ul>';
				html += '</p>';
			}, this);
 
			Ext.create('NestedMVC.view.Window', {
		        title: 'Authors & Books',
	            html: html
		    }).show();
		}, scope: this});
	}
 
});
 

En haut du fichier on voit que j’étends la classe Ext.app.Controler, puis j’indique à ma class que j’ai besoin d’une classe en plus avec l’instruction requires. C’est juste une vue que j’utiliserai pour afficher les résultats dans une fenêtre Ext.

Ensuite je définis les stores que je veux utiliser dans ce controller. Cette fois, je n’indique que le nom de la class et plus sa définition complète. ExtJs va automatiquement me créer un accesseur pour chacun de mes Stores. Il sera sous la forme get + nom_du_store + Store()

this.getAuthorsStore(); //pour le store Authors
this.getBooksStore(); //pour le store Books
this.getNestAuthorsStore(); //pour le store NestAuthors

Attardons-nous un peu sur la function init().

init: function()
{
	var me = this;
    this.control({
    	'#authorButton': {
    		click: me.showAuthor
    	},
    	'#bookButton': {
    		click: me.showBook
    	},
    	'#nestauthorButton': {
    		click: me.showNested
    	}
    });
},
 

Dans cette fonction j’utilise la méthode control() qui permet de définir des actions (handler) sur des évènements  pour les éléments de la vue. Dans mon viewport, j’ai spécifier un id pour chacun de mes boutons, ce qui me permet de facilement les récupérer dans le controller. En interne la méthode control() utilise Ext.ComponentQuery pour trouver les composants ExtJs des vues.

Je pense que le reste du code du controller parle de lui-même, j’ai définis une fonction pour chaque bouton. A l’intérieur je charge les données dans le store puis je le parcours et retourne le contenu dans une fenêtre.

 

7. Création de la fenêtre

Dans le controller j’utilise une fenêtre pour afficher les résultats. Voici sa définition.

/nested_models_mvc/view/Window.js

Ext.define('NestedMVC.view.Window', {
	extend: 'Ext.window.Window', 
 
    height: 400,
    width: 600,
    autoScroll: true,
 
    initComponent: function()
    {
		this.callParent(arguments);
    }
});
 

 

8. Finalisation

Il ne reste plus qu’à compléter le front controller et à créer un fichier HTML pour charger les fichiers javascript.

Ext.application({
    name: 'NestedMVC',
 
    autoCreateViewport: true,
 
    appFolder: '/nested_models_mvc',
 
    models: ['Author', 'Book'],
    stores: ['Authors', 'Books', 'NestAuthors'],
    controllers: ['PageControl'],
 
    launch: function() {
    }
});
 

J’ai ajouté les noms des Model, Store et Controller que j’utilise.

Je crée un fichier HTML en dehors du dossier nested_models_mvc.

<html>
<head>
    <title>Test: colonne editable conditionnelle</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-debug.js"></script>
 
 
</head>
<body>
 
 
<script type="text/javascript">
	Ext.Loader.setConfig({enabled: true, disableCaching: true});
	Ext.Loader.setPath('Ext', '/ext-4.0.2a/src');
 
</script>
<script type="text/javascript" src="nested_models_mvc/app.js"></script>
 
 
 
</body>
</html>
 

Il ne reste plus qu’à lancer la page dans le navigateur et à cliquer sur les boutons.
Le modèle MVC de ExtJs est très intéressant même s’il oblige un code plus verbeux et plus long à écrire, il permet une séparation des couches (MVC oblige) et une factorisation du code possible pour les gros projets. Attention aux pièges dans la définition des Stores et des Models, qui diffèrent d’un code plus old school.

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

 

Il y a 3 commentaires.

Ecrit par Ecocdk le 9 août 2012

Salut, merci pour ton tuto, très intéressant, cela m'aide grandement car suis rookie en ExtJs. J'ai quelques questions : - Pour l'appel des 'models', 'stores' et 'views', tu le fais au niveau de Ext.application. Ne vaudrait-il pas mieux le faire au niveau du controller (qui lui est appelé dans Ext.application justement) ? - cet appel des models des stores et des views ne te permettrait -il pas d'éviter les 'requires' ? - enfin, est-il possible d'avoir deux fichiers json et faire la relation entre les deux dans hasMany ou bien ne peut-il y avoir qu'un fichier json contenant des toutes les données préalablement imbriquées ? Encore merci pour l'tuto. A+ Cédrik

Réponse de Ulrich le 9 août 2012

Bonjour, J'ai écris cet article en suivant les premiers guides fournis par Sencha sur leur model MVC. En effet il ne faut pas lister les Stores, les Models et les View dans l'application, ça empêche de créer le fichier JSB3 avec le SDK de Sencha. La déclaration des Stores, Models et Views doit être dans les controllers. Pour les requires, malheureusement je n'ai pas encore trouvé la règle 'ultime'. Je liste systématiquement les Views relatives à chaque controller dans le bloc require et ensuite j'itère à la création du JSB3 pour trouver les require manquant. Je n'ai pas de réponse pour les fichiers Json, je n'ai pas eu besoin de gérer les relations en très les models dans mes projets en ExtJs. Bon courage.

Ecrit par Ecocdk le 9 août 2012

Salut, merci pour ta rapide réponse, en tout cas ton tuto est bien foutu et cela m'aide grandement. A+ Cédrik

Ajouter un commentaire