Ellipse Tutorial

Comment utiliser le composant <web:DataTable> ?



ATTENTION : Tutorial en cours d'écriture ! N'hésitez pas à nous signaler toute erreur ou suggestion.

Accès rapide :
   Quelques rappels sur le pattern MVC
   Définition d'un modèle de données en Javascript
   Redimentionnement des colonnes

Il est fréquent, lors du développement d'une application Web, d'avoir à présenter des données dans une table plus ou moins complexe. D'ailleurs, ce besoin existe aussi pour d'autres types d'applications : par exemple, la librairie Java Swing propose le composant JTable pour répondre à ce type de besoin. Ce n'est pas pour rien que je vous parle du composant JTable : effectivement, le framework Ellipse s'en inspire en bonne partie, tout en adaptant son architecture aux besoins du Web (transport via HTTP des données, par exemple). Le premier point commun à signaler réside dans le fait que les deux solutions cherchent à mettre en oeuvre un pattern MVC.

Quelques rappels sur le pattern MVC

Le pattern MVC (Model / View / Controller) est un pattern d'architecture qui vise à séparer le code d'un composant d'IHM en trois parties : le modèle de données (il définit l'ensemble des données à présenter), la (ou les) vue(s) (utiliser pour afficher les données) et enfin le controlleur (qui reçoit les interractions de la part de l'utilisateur de l'IHM). Le fait de coder un composant d'IHM en suivant ce pattern permet de garantir une meilleure structuration du code (chaque rôle est clairement établit) et donc induit une meilleure prise en main de ce code et une meilleure maintenabilité de ce dernier.

Un exemple "cas d'école" : imaginons que nous devions coder un jeu d'échec (avec un language de programmation quelconque). Les informations nécessaires à la connaissance de l'état de la partie sont : la position des 32 pièces sur l'échiquier, la connaissance des pièces mortes, le personne en train de jouer et les valeurs des deux chronomètres. Ces informations seront stockées dans le modèle. Elles ne sont pas graphiques et si on les sauvergarde sur disque, on a alors sauvegardé la partie. Il sera possible de la recharger et de la réexecutée ultérieurement. Ensuite, il faut présenter la partie en cours à (ou aux) utilisateur(s) de l'application : c'est le rôle des vues. Une vue affiche le modèle. L'avantage et de pouvoir utiliser des vues différentes sur un même modèle. Dans le cas de notre jeu d'échec, la partie en cours pourrait être présentée d'au moins deux manières : une vue en 2D ou bien une vue en 3D. Quelque soit le choix de la vue présentée à l'utilisateur, c'est bien le même modèle qui est utilisé. Enfin, le contrôlleur recoit les interractions de l'utilisateur pour mettre à jour le modèle et donc impacter l'affichage produit par la vue.

De nombreuses librairies (MFC en C++, Swing (pour Java), ...) et de nombreux framework (Struts, JSF, ...) proposent souvent d'implementer des patterns MVC à un plus ou moins haut niveau d'abstraction. C'est donc aussi le cas de la librairie JWT. A titre d'information, voici un petit diagramme UML montrant le modèle de classes retenu pour le pattern MVC du composant DataTable.

Définition d'un modèle de données en Javascript

Comme nous l'avons déjà dit dans un chapitre précédent, la notion d'interface n'existe pas à proprement parler en Javascript. Néanmoins, le framework Ellipse (et plus précisément la librairie JWT) propose un mécanisme pour approcher ce concept. Théoriquement, une interface permet de définir un contrat (un ensemble de méthodes) et de vérifier à la compilation que vos classes implémentations (qui redéfinnissent les méthodes de l'interface) fournissent bien une implémentation pour chaque méthode : dans le cas inverse une erreur de compilation peut être levée. Or nous avons un problème : en Javascript, il n'y a pas de compilateur.

Pour contourner ce problème, nous proposons de définir une pseudo interface en codant une classe (qui définira donc le contrat sous forme d'un ensemble de méthodes) pour laquelle chaque méthode déclenchera dans tous les cas une exception. Ainsi, si les méthodes de cette classes ne sous pas redéfinie dans une sous classe, des exceptions seront inexaurablement déclenchées. Pour de plus amples informations sur cette technique, je vous renvoit vers le chapitre relatif à la programmation orientée object avec la librairie JWT.

Pourquoi je viens de vous parler d'interface ? Tout simplement par ce qu'un MVC est souvent basé sur des interfaces. C'est aussi le cas pour le composant DataTable. Effectivement, il est capable d'afficher différents modèles de données. Pour ce faire, il vous faut coder votre modèle en respectant des règles particulières : celles induites par l'interface AbstractDataTableModel. En voici son contenu.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
/**
 * The DataTable JWT component is based on a MVC (Model/View/Controller) pattern. 
 * The AbstractDataTableModel pseudo-interface (Javascript not really supports
 * interface concept) is used to produce a model.
 * 
 * This interface specifies the contrat (a set of methods) that the JWT DataTable
 * component will use to obtain the data from a model. To simulate interface concept, all the
 * methods of the AbstractDataTableModel throws an AbstractException
 * if there are invoked. You must override each methods of this interface to produce a correct
 * model.
 * 
 * CAUTION: the methods of this interface can be invoked frequently. Your overriding methods 
 * should be quick. Otherwise the rendering of the DataTable component can be slow.
 * 
 * @since 0.3.6
 * @author Alexia Ramaioli & Dominique Liard
 */
var AbstractDataTableModel = Object.extendClass({
    
    /**
     * Returns the number of rows that the model have, and that the DataTable
     * component must display.
     * @return The number of rows.
     */
    getRowCount : function() {
        throw new AbstractException();
    },

    /**
     * Returns the number of columns that the model have, and that the DataTable
     * component must display.
     * @return The number of columns.
     */
    getColumnCount :    function() {
        throw new AbstractException();
    },
    
    /**
     * Returns the name for the column of the specified columnIndex. The name of a column is
     * displayed in the header of the DataTable component.
     * @param columnIndex The index of the column for which obtain the name. This index is zero based.
     * @return The name for the specified column.
     */
    getColumnName : function( columnIndex ) {
        throw new AbstractException();
    },

    /**
     * Returns the editable state for a particular cell of this data model.
     * @param rowIndex      The index of the row for which obtain the state. This index is zero based.
     * @param columnIndex   The index of the column for which obtain the state. This index is zero based.
     * @return The editable state for the specified cell.
     */
    isCellEditable : function( rowIndex, columnIndex ) {
        throw new AbstractException();
    },
    
    /**
     * Returns the value for a particular cell of this data model. The associated DataTable
     * component will show this value for the considered cell.
     * @param rowIndex      The index of the row for which obtain the value. This index is zero based.
     * @param columnIndex   The index of the column for which obtain the value. This index is zero based.
     * @return The value for the specified cell.
     */
    getValueAt : function( rowIndex, columnIndex ) {
        throw new AbstractException();
    },

    /**
     * This method permit to set the value of a particular cell of this data model.
     * @param rowIndex      The index of the row for which set the value. This index is zero based.
     * @param columnIndex   The index of the column for which set the value. This index is zero based.
     * @param cellValue     The new value for the specified cell.
     */
    setValueAt : function( rowIndex, columnIndex, cellValue) {
        throw new AbstractException();
    }

});
Interface AbstractDataTableModel.js

En ce basant sur cette pseudo-interface, nous allons maintenant définir notre modèle de données. A titre d'exemple, nous allons afficher une table de données présentant quelques informations sur les principaux éléments de notre système solaire (soleil, planètes et planètes naines). Voici l'implementation de notre modèle.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
importPackage( "corelib/services/web/javascript/jwt/tables/AbstractDataTableModel.js" );

var TestDataTableModel = AbstractDataTableModel.extendClass({    

    initialize: function(name) {
        this._titles =
            new Array( "Planets",  "Type",    "Aphelion", "Perihelion","Orbital Period", "Satellites" );
        
        this._values= new Array(
            new Array( "Sun",      "Star",          "&#160;",   "&#160;",    "&#160;",      "&#160;"  ),
            new Array( "Mercury",  "Planet",        "0.466 au", "0.307 au",  "87.97 d",     "0" ),
            new Array( "Venus",    "Planet",        "0.728 au", "0.718 au",  "224.70 d",    "0" ),
            new Array( "Earth",    "Planet",        "1.016 au", "0.983 au",  "365.25 d",    "1" ),
            new Array( "Mars",     "Planet",        "1.665 au", "1.381 au",  "686.96 d",    "2" ),
            new Array( "Ceres",    "Dwarf planet",  "2.987 au", "2.544 au",  "1679.82 d",   "13" ),
            new Array( "Jupiter",  "Planet",        "5.46 au",  "4.65 au",   "4335,35 d",   "63" ),
            new Array( "Saturn",   "Planet",        "10.05 au", "9.02 au",   "10747 d",     "61" ),
            new Array( "Uranus",   "Planet",        "20.08 au", "18.37 au",  "30799 d",     "27" ),
            new Array( "Neptune",  "Planet",        "30.32 au", "29.81 au",  "60224 d",     "13" ),
            new Array( "Pluto",    "Dwarf planet",  "49.31 au", "29.66 au",  "90613 d",     "3" ),
            new Array( "Haumea",   "Dwarf planet",  "51.54 au", "35.02 au",  "104009 d",    "2" ),
            new Array( "Makemake", "Dwarf planet",  "52.57 au", "38.71 au",  "112000 d",    "0" ),
            new Array( "Eris",     "Dwarf planet",  "97.56 au", "37.77 au",  "203450 d",    "1" )
        );
    },

    getValueAt: function(row, column) { 
        return this._values[row][column];        
    },
    
    setValueAt: function(row, column, value) { 
        this._values[row][column]= value;        
    },
    
    getRowCount: function() { 
        return this._values.length;
    },
    
    getColumnCount: function() { 
        return this._titles.length;
    },
    
    getColumnName: function(column) { 
        return this._titles[column];
    },
    
    isCellEditable: function(row, column) { 
        return false
    }
    
});
L'implémentation de notre modèle de données

Notez qui serait aussi possible de coder un modèle allant chercher ses données sur un serveur HTTP par le biais d'un service Web. Nous reviendrons sur cette possibilité ultérieurement dans ce document.

Nous pouvons maintenant afficher notre premier composant DataTable basé sur notre modèle. Pour ce faire, il faut instancier un composant DataTable en passant à sa méthode d'initialisation le modèle de données à utiliser. Ensuite, il faudra injecter la table dans la page HTML. Notez qu'une vue par défaut vous est proposée. Le code à produire pour un affichage simple est donc très simple. En voici un petit exemple. Notez que l'exemple proposé est affiché juste en dessous de l'extrait de code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
<html>
    <head>
        <title>DataTable Sample</title>
        <script language="Javascript" src="corelib/services/web/javascript/Core.js"></script>
        <link rel="stylesheet" type="text/css" href="corelib/services/web/javascript/jwt/Jwt.css" />
        <style>
            #forDataTable .JwtDataTable {
                width: 90%;
                height: 150px;
            }
        </style>        
        <script language="Javascript"><!-- 
            importPackage( "corelib/services/web/javascript/jwt/tables/DataTable.js" );
            importPackage( "DataTableSamples.js" );            // For load your DataTableModel
            
            var model = new TestDataTableModel();
            var dataTable = new DataTable( model );

            addEventHandler(window, "onload", function (event) {    
                dataTable.injectInDocument(document.getElementById("forDataTable"));
            });                            
        //--></script>
        
    </head>
    <body>
        <h1 id="title">DataTable Sample</h1> <br/>

        <p style="color: red; font-weight: bold; font-size: 10pt;">
            Click the right mouse button to view the HTML/Javascript source code. <br/>
            Tables columns are resizable. DataTableModel is defined in a specific file.
        </p> <br/>

        <div align="center">
            <div id="forDataTable"></div>
        </div>
    </body>
</html>
Utilisation de votre modèle dans une DataTable


Testez un peu la table proposée ci-dessus. Premier point, la barre de scrolling permet de visualiser toutes les informations présentes dans la table. En second lieu, notez que vous pouvez retailler les tailles des différentes colonnes. Pour ce faire, placez la souris entre deux cellules de la ligne de titre : l'icône de la souris devrait changer. Vous pouvez alors retaillez les cellules en gardant le bouton gauche de la souris enfoncé.

Redimentionnement des colonnes


ATTENTION : Ellipse Framework vous est proposé en version beta (d'évaluation) afin de vous permettre d'évaluer ce framework. Infini Software se dégage de toutes responsabilités relatives à l'utilisation de ce framework. De plus, Infini Software ne pourra nullement être tenu responsable de l'utilisation des informations présentes dans ces tutoriaux.

Dominique LIARD - © 2007..2010 SARL Infini Software - Tous droits réservés
Les autres marques et les noms de produits cités dans ces documents sont la propriété de leurs détenteurs respectifs.