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.
Réponse de Ulrich le 9 août 2012