Les widgets

Introduction

Le widget est un composant d’interface graphique (n’importe quel élément de type HTML/JavaScript) et est aussi un outils permettant de récupérer des informations via le server, ou une source de données interne/externe.

Le but des widgets est donc de pouvoir développer ses propres composants clients sans avoir besoin de modifier le code de Easy geoweb. Il doit être interprété par le client de façon générique et autonome.

Ce chapitre présente en détails la composition, les dépendances et la configuration des widgets.

Architecture

Chaque widget est indépendant de l’autre. Il est composé d’un fichier de description XML, d’un fichier source JavaScript et d’un fichier de style CSS :

Architecture d’un widget
gcweb-reference-img/lbs-integration/geoweb-easy/widgets_2.png

Le client est composé d’un gestionnaire de widgets au niveau JavaScript et est étoffé d’un ensemble de widgets.

Architecture globale des widgets
gcweb-reference-img/lbs-integration/geoweb-easy/widgets_1.png

Le principe est de décrire le widget via un fichier de configuration XML qui pointera vers le fichier source correspondant. Lors du chargement du widget par le moteur de widgets (server), celui-ci parsera le fichier XML pour en extraire les informations nécessaires. Il mettra ces informations sous forme de JSON qu’il renverra au client. Ensuite le client appellera la combo pour charger chaque fichier JS/CSS à l’aide des informations contenues dans le JSON.

Fichier XML

Description générale

Le fichier XML est un fichier de description du widget. Il est parsé par le moteur de widget pour en extraire les informations qui lui seront utiles et les renvoyer au client sous forme JSON. Ce fichier doit respecter les mêmes règles pour tous les widgets :

  • id : l’id du widget doit être unique
  • icon : le chemin relatif (par rapport au jar) de l’icône du widget
  • js : le fichier source correspondant
  • module : le module YUI du widget, il doit être unique et identique à celui du fichier source
  • display : les widgets peuvent hériter d’autres widgets ou de classes YUI que nous n’avons pas besoin forcément d’afficher, cet attribut permet de spécifier si l’on veut afficher le widget dans le builder/portail ou non
  • groups : les groupes définissent les utilisateurs qui auront accès au widget dans le cas d’un portail privé. Il est possible de définir les groupes directement dans l’outil de conception
  • lang : le fichier de ressources permet l’internationalisation du widget. Le chemin fait référence à un fichier properties. Dans l’exemple ci-dessous, le fichier recherché est par défaut resources/widgets.properties. Un widget peut supporter plusieurs langues par exemple resources/widgets_fr.properties ou resources/widgets_en.properties. La langue sera affiché en fonction de la langue du navigateur.
  • category : la catégorie permet de déterminer l’emplacement du widget dans la bibliothèque du Composer. Si ce champ n’est pas renseigné, la catégorie sera définie en fonction du répertoire où sera stocké le widget. La liste des catégories est disponible dans le fichier EasyGeowebCategories.xml. A partir de ce fichier, il est possible de rajouter de nouvelles catégories (voir la liste des catégories).
  • position : Le champ position permet de définir l’emplacement du widget dans la bibliothèque de widget. Si cette valeur n’est pas renseignée, le widget sera placé à la fin.
<widget id="geocoder"
        category="navigation"
        position="1"
    icon="images/geocoder.png"
    js="view/GeocoderWidget.js"
    module="geoconcept-widget-geocoder"
    display="true"
        groups="Standard"
        lang="resources/widgets">
</widget>

Il est possible d’ajouter des propriétés pour configurer le widget dans le Composer dans le fichier XML. En fonction de ce qui est renseigné, les propriétés sont automatiquement générées dans la fenêtre de configuration des widgets du Composer.

Le label de la propriété affichée est structuré ainsi, "widget." + id du widget + "." + id de la propriété. La valeur à ajouter dans le fichier properties est par exemple "widget.geocoder.mode=Mode d’affichage".

Le champ params permet de déterminer une valeur par défaut à la propriété :

<widget id="geocoder"
                category="navigation"
                position="1"
                icon="resources/images/Geocode_32.png"
                js="navigation/GeocoderWidget.js"
                module="geoconcept-widget-geocoder"
                display="true"
                groups="Standard"
                lang="resources/widgets">
        <properties>
                <property id="mode" type="radiobutton-2">
                        <params value="0"/>
                </property>
        </properties>
</widget>
Liste des propriétés

Voici la liste des propriétés disponibles pour les widgets :

  • checkbox : manipule les valeurs true ou false.
<property id="checkboxProperty" type="checkbox">
        <params value="true"/>
</property>
  • text : manipule des valeurs textuelles.
<property id="textProperty" type="text">
        <params value="default text"/>
</property>
  • slider : manipule des valeurs numériques. Les valeurs maximales sont déterminées par min et max. La valeur sélectionnée correspond à value.
<property id="sliderProperty" type="slider">
        <params min="1" max="12" value="6"/>
</property>
  • colorpicker : manipule des valeurs hexadécimales.
<property id="colorpickerProperty" type="colorpicker">
        <params value="#ffffff"/>
</property>
  • combobox : manipule des valeurs textuelles. La liste des valeurs disponibles se base sur les getComboboxOptions et setComboboxOptions (voir l'API des widgets).
<property id="comboboxProperty" type="combobox">
        <params value="comboboxValueName"/>
</property>
  • datagrid : manipule des valeurs textuelles. Les valeurs par défaut sont séparées par des ;. La liste des valeurs disponibles se base sur les getDgOptions et setDgOptions (voir l'API des widgets).
<property id="datagridProperty" type="datagrid">
        <params value="datagridValueName"/>
</property>
  • radiobutton-2 : manipule des valeurs numérique. Les valeurs possibles sont 0 ou 1. Pour afficher un label à chaque radiobouton, il est nécessaire d’ajouter 2 clés dans le fichier properties en les terminant par ".left" et ".right"
<property id="radiobuttonProperty" type="radiobutton-2">
        <params value="0"/>
</property>
  • checkbox-2 : manipule des valeurs numérique. Fonctionne à l’identique de la propriété "radiobutton-2" sauf qu’il est possible de sélectionner les 2 valeurs de la propriété.
<property id="checkbox2Property" type="checkbox-2">
        <params value="0"/>
</property>

Chacune de ces propriétés peut ensuite être utilisées dans le widget en utilisant la méthode getPropertyValue (voir l'API des widgets).

Liste des catégories

Un ensemble de catégories sont définies dans un fichier de configuration du projet EasyGeowebCategories.xml. Chaque widget est dépendant d’une de ces catégories. Pour ce faire le widget doit être placé dans un répertoire comportant le nom d’une catégorie. Exemple: notre widget geocode est placé dans navigation/Distance.js, ainsi il appartient à la catégorie navigation. Le widget distance est placé dans measure/Distance.js donc il appartient à la catégorie measure.

Il est possible d’ajouter de nouvelles catégories dans EasyGeowebCategories.xml. Pour cela il suffit d’ajouter une balise entry. key permet de définir le positionnement dans l’affichage de l’accordéon.

<properties>
        <entry key="0">layout</entry>
        <entry key="1">general</entry>
        <entry key="2">navigation</entry>
        <entry key="3">view</entry>
        <entry key="4">measure</entry>
        <entry key="5">annotation</entry>
        <entry key="6">data</entry>
        <entry key="7">modification</entry>
</properties>

Le nom de la catégorie permet de gérer son internationalisation. Par exemple, pour une nouvelle catégorie nommée graphic, il faudra ajouter la clé suivante dans easy.properties

accordion.category.graphic = graphique

Fichier JavaScript

WidgetBase

Le fichier JavaScript est le fichier source du widget. Celui-ci sera chargé par la combo pour être injecté dans le client. Voici le squelette d’un fichier source :

YUI.add("module-name", function(Y) {

        function WidgetClassName(config) {
        WidgetClassName.superclass.constructor.apply(this, arguments);
        }

        WidgetClassName.NAME = "WidgetClassName";

        Y.extend(WidgetClassName, Y.GCUI.WidgetBase,  {
            initializer: function(config) {
            },

            destructor: function() {
            },

            getWidget: function() {
                // return html description widget
            },

            configureAction: function() {
                WidgetClassName.superclass.configureAction.apply(this);

                // some actions
            }

        });
        Y.namespace("GCUI");
        Y.GCUI.WidgetClassName = WidgetClassName;

}, "1.0.0", {requires: ["project-widgetbase"] });

La fonction getWidget() sera appelée dans un premier temps par le WidgetManager (client) pour afficher le widget. Ainsi cette méthode renvoie la représentation du widget. Elle peut contenir tout ce que le développeur désire afficher. La seule contrainte est de rester dans une balise HTML <div></div>. En effet le widget ne sera affiché que s’il est contenu dans cette balise.

WidgetBase permet d’afficher des TreeView, des listbox, des combobox etc.

La fonction configureAction() sera appelée par le WidgetManager pour configurer le widget une fois celui-ci inséré dans le DOM. WidgetBase propose un série de fonction de base formant l'API des widgets. Ainsi tous les widgets ont accès à la carte, au détail du projet mais aussi à un panel que l’on peut utiliser pour y afficher diverses informations.

Tous les widgets héritant de WidgetBase possèdent des propriétés par défaut : Affichage de l'étiquette, label de l'étiquette et affichage d’un séparateur d'étiquette.

Cette partie la section intitulée « Widgets par l’exemple » ainsi que le tutorial présente des cas concrets d’utilisation des méthodes du WidgetBase.

WidgetButton

WidgetButton fonctionne de la même façon que WidgetBase, il suffit d’hériter de cette classe et de mettre le « requires project-widgetbutton ».

Il existe 3 types de boutons à définir dans initializer() :

  • BUTTON : bouton de base, l’action s’execute au moment du clic souris
  • TOOL : suite au clic souris, le bouton s’active et reste activé. Pour le désactiver, il faut cliquer sur un autre bouton de type TOOL.
  • TOGGLE : un clic souris active le bouton, un second clic le désactive.

Voici le squelette d’un WidgetButton

YUI.add("geoconcept-widget-button", function(Y) {

        function SchemaButton(config) {
                SchemaButton.superclass.constructor.apply(this, arguments);
        }

        SchemaButton.NAME = "schemaButton";

        Y.extend(SchemaButton, Y.GCUI.WidgetButton,  {
                initializer: function(config) {
                        SchemaButton.superclass.initializer.apply(this);
                        this.setType(Y.GCUI.WidgetButton.TYPE_TOOL);
                },

                destructor: function() {
                        SchemaButton.superclass.destructor.apply(this);
                },

                configureAction: function() {
                        SchemaButton.superclass.configureAction.apply(this);
                },

                // Button type action
                trigger:function() {
                        SchemaButton.superclass.trigger.apply();
                        // some actions
            },

                // Toggle and tool type action
                activate:function() {
                        SchemaButton.superclass.activate.apply();
                        // some actions
            },

            // Toggle and tool type action
            deactivate:function() {
                SchemaButton.superclass.deactivate.apply();
            }

        });
        Y.namespace("GCUI");
        Y.GCUI.SchemaButton = SchemaButton;

}, "1.0.0", {requires: ["project-widgetbutton"] });

Tous les widgets héritant de WidgetButton possèdent des propriétés par défaut : Affichage de l'étiquette, label de l'étiquette et widget par défaut s’il s’agit d’un bouton de type toggle ou tool.

API
Méthode

getJson() : {JSON} Retourne la description JSON du widget.
getJsonProject() : {JSON} Retourne la description JSON du projet.
getJsonLayers() : {JSON} Retourne la description JSON de la liste des couches définie pour le gestionnaire de couches
getWidgetName() : {String} Retourne le nom internationalisé du widget.
getWidgetDescription() : {String} Retourne la description internationalisé du widget.
getEGWApi() : {Y.GCUI.EGWRestAPI} Retourne l'objet EGWRestAPI afin d'avoir accès aux méthodes du service Rest de Easy-geoweb.
getParameters() : {JSON} Retourne les paramètres par défaut de l'application.
getMap() : {GCUI.Map} Retourne l'objet carte.
getNode() : {Node object} Retourne le widget sous format javascript.
getContextUrl() : {String} Retourne le contexte de l'url par exemple "geoweb".
getWidgetPath() : {String} Retourne l'url pour accéder aux fichiers présent dans le JAR par exemple "geoweb/widget?".
getHost() : {String} Retourne le host de l'url par exemple "http://localhost:8080/".
getImageSrc() : {String} Retourne l'url pour accéder à l'image définie dans icon du fichier XML.
isPortal() : {Boolean} Retourne true si l'application est démarrée dans le portail, false si c'est dans l'outil de conception.
getWrapper() : {String} Retourne le wrapper, utilisé pour désactiver le widget dans l'outil de conception.
getPropertyValue(id) : {String} Retourne la valeur de la propriété définie dans le fichier XML en fonction de son identifiant (id).
updatePropertyValue(id,value) : Met à jour la valeur (value) de la propriété définie dans le fichier XML en fonction de son identifiant (id).
getPropertiesPanel() : {Node object} Retourne l'objet panneau de propriété sous l'accordéon dans le but d'ajouter de nouvelles propriétés.
emptyPropertiesPanel() : Vide le panneau de propriété.
addDynamicProperty(order, propertyId, label, type, defaultValue, defaultMin, defaultMax) : Permet de créer une propriété sans
le renseigné dans le fichier XML du widget. order comme position d'insertion parmi les autres propriétés. propertyId comme
identifiant. label d'affichage. type, à choisir parmi les types disponibles (par exemple "text"). defaultValue, defaultMin
et defaultMax pour définir les valeurs par défaut, minimale (optionnel) et maximale (optionnel).
movePropertyToOrder(property, order) : Déplacer une propriété. property comme identifiant de la propriété et order en numéro de
placement parmi les autres propriétés.
getComboboxOptions(id) : List{String} Retourne la liste des valeurs disponibles pour la propriété "combobox"
en fonction de l'id de la propriété.
setComboboxOptions(id, data) : Enregistre en mémoire la liste des valeurs pour la propriété "combobox" avec en paramètre l'id de la
propriété et data en liste de string.
getDgOptions(name) : List{String} Retourne la liste des valeurs disponibles pour la propriété "datagrid"
    en fonction de l'id de la propriété.
setDgOptions(name, data) : Enregistre en mémoire la liste des valeurs pour la propriété "datagrid" avec en paramètre l'id de la
propriété et data en liste de string.
_(key) : {String} Converti une clé en chaine de caractère internationalisable.
Méthode vide à enrichir pour utilisation

reinitView() : Méthode permettant de réinitialiser l’action du widget.

getWidget() : {String} Méthode permettant de définir la représentation du widget.
checkboxPropertyAction(propertyId, value) : Action appelée après l'enregistrement d'une propriété de type checkbox. PropertyId
comme identifiant de la propriété enregistré. Value comme valeur enregistré.
textPropertyAction(propertyId, value) : Action appelée après l'enregistrement d'une propriété de type text. PropertyId
comme identifiant de la propriété enregistré. Value comme valeur enregistré.
hiddenPropertyAction(propertyId, value) : Action appelée après l'enregistrement d'une propriété utilisant des input de type
hidden (datagrid, combobox...). PropertyId comme identifiant de la propriété enregistré. Value comme valeur enregistré.
radioPropertyAction(propertyId, value) : Action appelée après l'enregistrement d'une propriété de type radiobutton. PropertyId
comme identifiant de la propriété enregistré. Value comme valeur enregistré.

L’ensemble des méthodes de WidgetBase sont réutilisables dans WidgetButton.

Constante

WidgetButton.NAME_PANEL : Nom du panneau

WidgetButton.TYPE_TOGGLE : Bouton de type Toggle (activable et désactivable par l'utilisateur).
WidgetButton.TYPE_BUTTON : Bouton de type Bouton (action executé au moment du clic).
WidgetButton.TYPE_TOOL : Bouton de type Tool (un seul bouton de type Tool peut être est activé, si l'utilisateur active un autre bouton de type Tool, tous les autres sont désactivés).
Méthode

isActive() : {Boolean} Retourne true si le bouton est activé, false si non.
getPanel() : {Panel node} Retourne l'objet panel lié au widget. Chaque widget possède son propre panel par défaut.
getWidget() : {Widget node} Retourne l'objet représentant le widget sous forme de bouton avec une icone défini dans le fichier XML.
reinitView() : Réinitialise le widget en désactivant le bouton.
createPanel(title) : Créé le panel par défaut avec le titre (title) en paramètre.
showPanel() : Affiche le panel.
hidePanel() : Masque le panel.
hidePopup() : Masque les popups de type _GCUI.Control.Popup_.
Méthode vide à enrichir pour utilisation

activate() : Méthode appelée suite à l’activation d’un bouton de type Tool ou Toggle.

deactivate() : Méthode appelée suite à l'désactivation d'un bouton de type Tool ou Toggle.
trigger() : Méthode appelée suite au clic sur un bouton de type Button.
onClosePanel() : Méthode appelée suite à la fermeture du panel.

Widgets par l’exemple

Voici dans le détail certains widgets pour étudier leur implémentation.

Hand

Ce widget permet de déplacer la carte. Il est de type bouton donc héritera de la classe WidgetButton.

YUI.add("geoconcept-widget-hand", function(Y) {

        /* Hand class constructor */
        function Hand(config) {
                Hand.superclass.constructor.apply(this, arguments);
        }

        /*
         * Required NAME static field, to identify the Widget class and
         * used as an event prefix, to generate class names etc. (set to the
         * class name in camel case).
         */
        Hand.NAME = "hand";

        Y.extend(Hand, Y.GCUI.WidgetButton,  {
                initializer: function(config) {
                        Hand.superclass.initializer.apply(this);
                        this.setType(Y.GCUI.WidgetButton.TYPE_TOOL);
                },

                destructor: function() {
                        Hand.superclass.destructor.apply(this);
                },

                configureAction: function() {
                        Hand.superclass.configureAction.apply(this);
                },

                activate:function() {
                        Hand.superclass.activate.apply();
                        var map = this.getMap();

                        DynMapSetMouseMode(map,DynMapMoveSelectionMode());
            },

            deactivate:function() {
                Hand.superclass.deactivate.apply();
            }

        });
        Y.namespace("GCUI");
        Y.GCUI.Hand = Hand;

}, "1.0.0", {requires: ["project-widgetbutton"] });

Ce widget reprend l’exemple standard décrit plus haut. La méthode activate() permet au widget d’appeler la méthode de l’API Javascript pour activer le mode de déplacement de la carte.

Voici son fichier de description XML :

<widget id="hand"
                icon="resources/images/hand.png"
                js="navigation/Hand.js"
                module="geoconcept-widget-hand"
                display="true"
                lang="resources/widgets">
</widget>

Le nom du module correspond à celui définit dans le fichier source. De plus on peut remarquer que le widget se trouvera dans la catégorie position de l’accordion.

MeasureArea

Ce widget permet de mesurer des surfaces.

YUI.add("geoconcept-widget-measure-area", function(Y) {

        [...]

        Y.extend(MeasureArea, Y.GCUI.MeasureBase,  {

                [...]

            /**
             * Manage widget action
             */
                activate:function() {
                        MeasureArea.superclass.activate.apply(this,[this.getCallbacks()]);
                        this.createPanelMeasure();
                        this.showPanel();
                },

                deactivate:function() {
                        MeasureArea.superclass.deactivate.apply(this);
                },

                /**
                 * Manage default panel
                 */
                // Create panel for area information
                createPanelMeasure:function() {
                var panelNode = this.createPanel(this._("widget.measureArea.popup.title"));
                        var id = this.getPanelId();

                var areaInfo = Y.Node.create("<div id='" + id + "' class='areaMeasure'></div>");
                areaInfo.append("<div class='areaSegmentTitle'>" + this._("widget.measureArea.segment.measure") + "</div>");
                areaInfo.append("<div id='" + MeasureArea.ID_PANEL_SEGMENT_MEASURE + id + "' class='areaSegmentValue'>0.000 " + this._("widget.measureArea.segment.kilometer") + "<sup>2</sup></div>");

                        panelNode.appendChild(areaInfo);
            }
        });
        Y.namespace("GCUI");
        Y.GCUI.MeasureArea = MeasureArea;

}, "1.0.0", {requires: ["geoconcept-widget-measure-base"] });

Ce widget étend Y.GCUI.MeasureBase utilisant le Control OpenLayers de mesure, OpenLayers.Control.Measure Ce widget utilise le panel pour afficher son résultat.

Layers

Le widget layers n’est pas un bouton mais affiche un TreeView sur un div donné.

YUI.add("geoconcept-widget-layers", function(Y) {

        [...]

        Y.extend(Layers, Y.GCUI.LayersBase, {

                [...]

                configureAction : function() {
                        Layers.superclass.configureAction.apply(this);

                        if (!this.isExistingLayers) {
                                var yuiNode = Y.one("#"+this.getId());
                                if (!this.isPortal()) {
                                        yuiNode.setContent(this.getWrapper());
                }
                                var map = this.getMap();
                                var wLayers = this;
                                var options = {
                                        div : document.getElementById(this.getId())
                                };
                                var ls = new GCUI.Control.LayerSwitcher(options);
                                map.addControl(ls);

                                var json = this.initJson();
                                ls.initFromJson(json);

                                yuiNode.addClass("addedWidget");

                                if (this.isPortal()) {
                        var layers = map.layers;
                                        for (var i = 0; i < layers.length; i++) {
                                                var layer = layers[i];
                                                layer.events.on({
                                    "visibilitychanged" : wLayers.layerChangeVisibility,
                                    scope : wLayers
                                });
                                        }
                                        this.showLegends(layers);
                }
                        }
                },

                [...]

        });
        Y.namespace("GCUI");
        Y.GCUI.Layers = Layers;

}, "1.0.0", {requires : ["geoconcept-widget-layers-base"] });

Ce widget étend Y.GCUI.LayersBase qui étend lui même Y.GCUI.WidgetBase. Le TreeView est généré par GCUI.Control.LayerSwitcher. Il affiche la liste des couches à afficher dans un JSON.

Contact

Ce widget a la particularité d’utiliser des propriétés, ces propriétés sont définies dans son fichier de description XML :

<widget id="contact"
                icon="resources/images/contact.png"
                js="general/Contact.js"
                module="geoconcept-widget-contact"
                display="true"
                lang="resources/widgets">
        <properties>
                <property id="mailContact" type="text"/>
        </properties>
</widget>

Depuis le client il sera donc possible de déterminer l’adresse mail dans un champ texte.

Le fichier source prend en compte ces propriétés qui lui sont injectées en JSON par le client. En effet, le moteur de widgets va lire le fichier XML du widget, injecter le JSON correspondant au client qui créera une instance du widget et le client se chargera d’instancier le widget avec le descriptif JSON. Ce descriptif JSON est setté à l’instance. A chaque changement d’une des valeurs des propriétés, le JSON est mis à jour. Lors de la sauvegarde du builder, le fichier JSON de chaque instance est récupéré pour être ensuite sauvegardé par le builder. (cf partie client)

Voici le code source du widget :

YUI.add("geoconcept-widget-contact", function(Y) {

        [...]

        Contact.MAIL_CONTACT_PROPERTY = "mailContact"

        [...]

        Y.extend(Contact, Y.GCUI.WidgetButton,  {

                [...]

                configureAction: function() {
                        Contact.superclass.configureAction.apply(this);
                        var defaultMail = this.getPropertyValue(Contact.MAIL_CONTACT_PROPERTY);
                        if (defaultMail == undefined || defaultMail == "") {
                                this.updatePropertyValue(Contact.MAIL_CONTACT_PROPERTY, this.getParameters().contact);
                        }
                },

                trigger: function() {
                        window.location = "mailto:" + this.getPropertyValue(Contact.MAIL_CONTACT_PROPERTY);
                }

        });
        Y.namespace("GCUI");
        Y.GCUI.Contact = Contact;

}, "1.0.0", {requires: ["project-widgetbutton"] });

La partie importante ici est la gestion des properties. En parsant le fichier XML, le moteur de widgets va appeler la addProperties() lors de la sélection de l’instance dans l’outil de conception si le widget possède des propriétés. updatePropertyValue() est utilisée ici pour mettre à jour la valeur de la propriété dans le JSON. Il sera ensuite sauvegardé dans la description du projet puis stocké dans la base de données.