Avant tout propos, la lecture des documentations de YUI et OpenLayers est fortement recommandée pour réaliser des widgets. De nombreux exemples sont à disposition YUI exemples et OpenLayers exemples. De plus, la communauté autour de ces 2 librairies est très active et de nombreuses réponses se trouvent sur les forums.
Ce tutorial présente plusieurs exemples pour créer son propre widget. Avant de commencer, il convient de s’assurer qu’Eclipse est installé sur votre poste ainsi que le module Maven. Il vous permettra de générer un fichier JAR qui contiendra vos widgets.
Easy geoweb accepte plusieurs fichiers JAR avec des widgets mais chaque widget doit posséder un identifiant unique.
Un widget est un module qui sera chargé dynamiquement dans le portail et qui se compose de 3 fichiers :
-
fichier XML
pour décrire les ressources nécessaires (javascript, properties, image) et pour déterminer l’identification du module. -
fichier JS
pour configurer les actions et les interactions du widget avec le portail. -
fichier CSS
pour paramétrer le style du widget.
L’ensemble des sources nécessaires à la réalisation de ce tutorial est fourni avec la documentation.
Dans Eclipse, créer un nouveau projet Maven.
Choisir Create a simple project (skyp archetype selection). Pour ce tutorial, la description de l'artifact est :
-
Group Id
: com.geoconcept.geoweb.product -
Artifact Id
: geoweb-easy-widgets-tutorial -
Version
: 0.0.1-SNAPSHOT -
Packaging
: jar
Une fois le nouveau projet créé, supprimer la classe principale java et le répertoire test et main dans src. Dans le répertoire src, créer un répertoire scripts avec Source Folder (clic droit sur le projet, New → Source Folder).
Ensuite créer un répertoire META-INF dans src. Il contiendra le fichier egw-widgets.txt. A l’intérieur de ce fichier, il est nécessaire d’inscrire une chaine de caractère, par exemple "easy-geoweb widgets module". Ce fichier permet d’identifier et de filtrer les JAR pour conserver uniquement ceux qui possède des widgets.
L’organisation finale est la suivante :
Créer 4 nouveaux répertoires dans src/scripts :
- general : contiendra les fichiers des nouveaux widgets. Ce répertoire sera visible dans l’accordéon du Composer. Il existe plusieurs catégories/répertoires disponibles pour le Composer (Annotation/annotation, Informations sur les objets/data, Général/general, Mise en page/layout, Mesures/measure, Modifications des données/modification, Navigation/navigation, Vue/view). En plus de general, ajouter les répertoires annotation et navigation pour réaliser les exemples.
- resources : contiendra les fichiers de ressources (images, localisation,…).
Dans resources créer :
- images : contiendra toutes les images utilisées
- exampleLang.properties : fichier contenant toutes les chaines de localisation par défaut. Pour ajouter de nouvelles langues aux widgets, il faut dupliquer ce fichier en ajoutant au nom du fichier la localisation souhaitée. Par exemple, pour une localisation française, le fichier devra se nommer exampleLang_fr.properties.
Attention, un JAR de widget doit contenir uniquement des fichiers liés aux widgets.
Dans le fichier pom.xml, ajouter les éléments suivants afin de construire le projet au format JAR :
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <classesDirectory>src</classesDirectory> <outputDirectory>D:/widgets</outputDirectory> <excludes> <exclude>**/pom.xml</exclude> </excludes> <archive> <addMavenDescriptor>false</addMavenDescriptor> </archive> </configuration> </plugin> </plugins> </build>
classesDirectory est le chemin d’accès aux fichiers des widgets.
outputDirectory est le chemin dans lequel sera déposé le fichier JAR généré.
Ce widget permet d’afficher la légende de la carte. Il s’agit d’un widget de type bouton. La légende correspond à une image affichée dans un panneau.
Le widget est nommé ExampleLegend.
Créer les 3 fichiers du widget dans le répertoire general :
- ExampleLegend.xml
- ExampleLegend.js
- ExampleLegend.css
Cet exemple est le plus simple. Voici la configuration du fichier de description :
<widget id="exampleLegend" category="standard" position="12" icon="resources/images/exampleLegend.png" js="general/ExampleLegend.js" module="geoconcept-widget-example-legend" display="true" lang="resources/exampleLang"> </widget>
-
id
: identifiant unique -
icon
: chemin d’accès à la ressource image pour l’afficher comme bouton. -
js
: chemin d’accès au fichier de configuration de l’action du widget. -
module
: nom du module qui sera appelé dans le widget manager pour charger l’instance du widget. -
display
: booléen pour déterminer si le widget sera affiché dans le Composer. Utiliser false pour créer un sous-widget qui proposera des fonctionnalités commune à d’autres widgets. -
lang
: chemin d’accès au fichier de localisation.
Pour plus d’informations, voir la description des fichiers XML dans le chapitre des widgets.
Le fichier js permet de construire un nouveau module. Le widget créé est un bouton simple, l’action sera executée à chaque fois que l’utilisateur effectuera une pression sur le bouton. La méthode appelée suite à un clic souris est trigger:function(). Cette méthode est définie dans WidgetBase mais il convient ici de la surcharger afin d’afficher l’image de la légende. Voici la méthode finale :
trigger:function() { var panelNode = this.createPanel(this._("widget.exampleLegend.popup.title")); var linkNode = Y.Node.create(this.getImageHtml()); panelNode.appendChild(linkNode); this.showPanel(); },
Les méthodes _(key), createPanel(name) et showPanel() appartiennent à l'API de base d’un widget. Chaque widget possède une fenêtre (panel) qui est liée avec un identifiant unique. Il est possible de créer d’autres fenêtres mais il faudra les définir directement de le widget. La clé de localisation doit être ajoutée au fichier properties du jar.
La partie HTML de l’intérieur de la fenêtre est défini par la méthode getImageHtml(). Un noeud est ensuite créé puis ajouté à la fenêtre :
getImageHtml:function() { var imageUrl = "/" + this.getContextUrl() + "/combo?resources/images/sample_legende.png"; var html = "<img src='" + imageUrl + "' class='imageLegend'>"; return html; }
Dans cette méthode, la balise HTML est créée. La méthode getContextUrl() appartient à l'API de base d’un widget. L’image est stockée dans les ressources du widget. Une classe est associée afin de définir la position de l’image dans la fenêtre.
A présent, voici la description complète de ce widget :
YUI.add("geoconcept-widget-example-legend", function(Y) { /* ExampleLegend class constructor */ function ExampleLegend(config) { ExampleLegend.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). */ ExampleLegend.NAME = "exampleLegend"; Y.extend(ExampleLegend, Y.GCUI.WidgetButton, { initializer: function(config) { ExampleLegend.superclass.initializer.apply(this); }, destructor: function() { ExampleLegend.superclass.destructor.apply(this); }, configureAction: function() { ExampleLegend.superclass.configureAction.apply(this); }, /* * Action after click on button */ trigger:function() { var panelNode = this.createPanel(this._("widget.exampleLegend.popup.title")); var linkNode = Y.Node.create(this.getImageHtml()); panelNode.appendChild(linkNode); this.showPanel(); }, /* * Get HTML description for image legend * return string html */ getImageHtml:function() { var imageUrl = "/" + this.getContextUrl() + "/combo?resources/images/sample_legende.png"; var html = "<img src='" + imageUrl + "' class='imageLegend'>"; return html; } }); Y.namespace("GCUI"); Y.GCUI.ExampleLegend = ExampleLegend; }, "1.0.0", {requires: ["project-widgetbutton"] });
Dans cet exemple, une classe a été ajoutée pour définir les marges autour de la légende pour améliorer son positionnement dans la fenêtre.
.imageLegend { margin: 5px; }
Voici les images nécessaires à la réalisation de ce widget.
Placer ces images dans le répertoire src/scripts/resources/images.
Par défaut, un bouton possède 2 clés de localisation, label et description. label est utilisé dans l’accordéon du Composer et description est affiché lorsque la souris survole le bouton.
Attention les clés doivent obligatoirement commencer par widget.(id du widget).(fin de la clé). Pour ce widget, la clé du label sera widget.exampleLegend.label
Voici les clés de base à placer dans le fichier properties :
widget.exampleLegend.label=Exemple de l\u00E9gende widget.exampleLegend.description=Afficher la l\u00E9gende principale
Voici la clé pour afficher un titre dans la fenêtre :
widget.exampleLegend.popup.title=L\u00E9gende
Ce widget permet de dessiner un point sur la carte. Il s’agit d’un widget de type bouton poussoir, en cliquant dessus, l’utilisateur active la fonctionnalité. Une propriété dans le Composer permettra de déterminer l’image stockée en base de données à afficher après un clic sur la carte.
Le widget est nommé ExampleDrawPOI.
Créer les 3 fichiers du widget dans le répertoire annotation :
- ExampleDrawPOI.xml
- ExampleDrawPOI.js
- ExampleDrawPOI.css
Cet exemple permet de rajouter une propriété au widget. Voici la configuration du fichier de description :
<widget id="exampleDrawPOI" category="standard" position="13" icon="resources/images/exampleDrawPOI.png" js="annotation/ExampleDrawPOI.js" module="geoconcept-widget-example-draw-poi" display="true" lang="resources/exampleLang"> <properties> <property id="defaultImage" type="combobox"> <params value="poi_blue_small"/> </property> </properties> </widget>
-
properties
: permet d’ajouter des propriétés au widget. -
property
: détail d’une propriété composé d’un id unique et d’un type. -
params
: paramètre par défaut pour la propriété correspondante. Cette balise n’est pas obligatoire dans le cas où il n’y a pas de valeur par défaut.
Pour plus d’informations, voir la description des fichiers XML dans le chapitre des widgets.
Le widget créé est un bouton de type TOOL, lorsque l’utilisateur clique sur le bouton, il reste activé. Un seul bouton de type TOOL peut être activé à la fois, tous les autres boutons de ce type sont alors désactivés. Les deux méthodes utilisés sont activate() et deactivate(). A l’activation, le widget appelle une fonction pour activer l'écouteur d’un clic sur la carte. Pour la désactivation, l'écouteur sera supprimé. Voici les 2 méthodes de base du widget :
activate:function() { ExampleDrawPOI.superclass.activate.apply(this); this.createPoiListener(); }, deactivate:function() { ExampleDrawPOI.superclass.deactivate.apply(this); this.removeCreatePoiListener(); },
La création d’un écouteur suite à un clic souris sur la carte est basée sur l’API Javascript
createPoiListener:function() { var wExampleDrawPOI = this; function clickListener(dynMap){} clickListener.prototype.onSelect = function(x,y,xpx,ypx){ wExampleDrawPOI.createObject(x,y); }; var listener = new clickListener(); DynMapAddMouseSelectionEventListener(this.getMap(), "clickOnMap", listener) },
La méthode s’appuie sur la fonction DynMapAddMouseSelectionEventListener dans laquelle il est possible de lui passer un écouteur (listener) en paramètre. Un clic souris active la fonction createObject(x,y) du widget. Les coordonnées x et y permettront de positionner l’objet sur la carte.
L’ajout du point se fait dans la couche objet de l’API Javascript. L’image affichée pour représenter le point est définie à partir d’une url.
var imageUrl = this.getHost() + this.getContextUrl() + "/Image/showImage.do?name=" + this.getPropertyValue(ExampleDrawPOI.IMAGE_PROPERTY_NAME);
Les méthodes getHost(), getContextUrl() et getPropertyValue(name) appartiennent à l'API de base d’un widget. ExampleDrawPOI.IMAGE_PROPERTY_NAME est une constante définie dans le module de la façon suivante :
ExampleDrawPOI.IMAGE_PROPERTY_NAME = "defaultImage";
getPropertyValue(name) est la méthode permettant de rechercher la valeur d’une propriété. Chaque widget peut bénéficier de
propriétés qu’il faut définir dans son fichier de description. Tous les widgets possèdent par défaut une propriété sur le
choix des groupes
ayant accès à ce widget dans le cas d’un portail privé. Les widgets de type bouton possèdent en plus une propriété pour définir
si le
widget est activé par défaut. Les autres propriétés sont créées automatiquement en fonction du fichier de description mais
si le type de
propriété n’existe pas, il est alors possible d’utiliser la méthode addProperties(groups)
.
Pour récupérer les images de la base de données, il faut faire appel à la méthode getImages de l’API REST. Le résultat est retourné dans la méthode onSuccess() ou onError(), en cas de succès ou d'échec de la recherche.
this.getEGWApi().getImages({ onSuccess:function(response) { }, onError:function(response) { } });
A la suite de la récupération des images, il faut associer le résultat à la liste déroulante de la propriété en utilisant
la méthode
setComboboxOptions(propertyId, data)
this.setComboboxOptions(ExampleDrawPOI.IMAGE_PROPERTY_NAME, images);
A présent, voici la description complète de ce widget :
YUI.add("geoconcept-widget-example-draw-poi", function(Y) { /* ExampleDrawPOI class constructor */ function ExampleDrawPOI(config) { ExampleDrawPOI.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). */ ExampleDrawPOI.NAME = "exampleDrawPOI"; ExampleDrawPOI.IMAGE_PROPERTY_NAME = "defaultImage"; Y.extend(ExampleDrawPOI, Y.GCUI.WidgetButton, { initializer: function(config) { ExampleDrawPOI.superclass.initializer.apply(this); this.setType(Y.GCUI.WidgetButton.TYPE_TOOL); this.getImages(); }, destructor: function() { ExampleDrawPOI.superclass.destructor.apply(this); }, configureAction: function() { ExampleDrawPOI.superclass.configureAction.apply(this); }, activate:function() { ExampleDrawPOI.superclass.activate.apply(this); this.createPoiListener(); }, deactivate:function() { ExampleDrawPOI.superclass.deactivate.apply(this); this.removeCreatePoiListener(); }, createPoiListener:function() { var wExampleDrawPOI = this; function clickListener(dynMap){} clickListener.prototype.onSelect = function(x,y,xpx,ypx){ wExampleDrawPOI.createObject(x,y); }; var listener = new clickListener(); DynMapAddMouseSelectionEventListener(this.getMap(), "clickOnMap", listener) }, // Remove create poi listener removeCreatePoiListener:function() { DynMapRemoveListener(this.getMap(),"click","clickOnMap"); }, // add points from list of citysite createObject:function(x,y) { var map = this.getMap(); var imageUrl = this.getHost() + this.getContextUrl() + "/Image/showImage.do?name=" + this.getPropertyValue(ExampleDrawPOI.IMAGE_PROPERTY_NAME); var poi = { mapx : x, mapy : y, imgsrc : imageUrl }; map.objectLayer.addObject(poi); map.objectLayer.refresh(); }, /** * Method: Get images on geoweb server. Call egw api rest method */ getImages : function() { var wExampleDrawPOI = this; this.getEGWApi().getImages({ onSuccess : function(response) { // For images properties, we add the images of geoweb // database var result = response.result; var images = [""]; for (i = 0; i < result.length; i++) { var image = result[i]; images.push(image.name); } wExampleDrawPOI.setComboboxOptions(ExampleDrawPOI.IMAGE_PROPERTY_NAME, images); }, onError : function(response) { alert("Poi getImages error " + response.status + ", " + response.message); } }); } }); Y.namespace("GCUI"); Y.GCUI.ExampleDrawPOI = ExampleDrawPOI; }, "1.0.0", {requires: ["project-widgetbutton"] });
Voici l’image nécessaire à la réalisation de ce widget.
Voici les clés de base à placer dans le fichier properties :
widget.exampleDrawPOI.label=Exemple de points widget.exampleDrawPOI.description=Cr\u00E9er un point sur la carte
Pour l’intitulé de la propriété du choix de l’image, il est nécessaire d’ajouter la clé suivante :
widget.exampleDrawPOI.defaultImage=Images :
Pour plus d’information sur les ressources, se référer au widget ExampleLegend.
Ce widget permet d’afficher les coordonnées X et Y suite à un clic sur la carte.
Le widget est nommé ExampleXY.
Créer les 3 fichiers du widget dans le répertoire navigation :
- ExampleXY.xml
- ExampleXY.js
- ExampleXY.css
Voici la configuration du fichier de description :
<widget id="exampleXY" category="standard" position="14" icon="resources/images/exampleXY.png" js="navigation/ExampleXY.js" module="geoconcept-widget-example-xy" display="true" lang="resources/exampleLang"> </widget>
Pour plus d’informations, voir la description des fichiers XML dans le chapitre des widgets.
Le widget créé est de type basique. Il ne possède aucune forme, c’est au développeur de réaliser son interface. L’ihm du widget est à définir dans la méthode getWidget(). Pour l’affichage dans l’outil de conception, le widget doit être désactivé. Pour cela, 2 méthodes sont disponibles dans l'API de base d’un widget, isPortal() et getWrapper(). La 1ère détermine si l’application est affichée dans le portail ou l’outil de conception. La seconde permet de récupérer le masque que l’on pourra superpositionner pour désactiver le widget.
getWidget: function() { var htmlContent = "<div class='xyBG'><div class='xyField'><label>"+this._("widget.exampleXY.x") + "</label></br><input type='text' id='x' size='15' disabled='disabled'/></div>" + "<div class='xyField'><label>" + this._("widget.exampleXY.y") + "</label></br><input type='text' id='y' size='15' disabled='disabled'/></div>" + "<div class='xyButton egw-btn'><button id='searchXY'>" + this._("widget.exampleXY.button") + "</button></div>"; if (!this.isPortal()) { htmlContent = htmlContent + this.getWrapper(); } htmlContent += "</div>"; return ("<div id='"+this.getId()+"'>"+htmlContent+"</div>"); },
Comme pour l’exemple ExampleDrawPOI, ce widget utilise un écouteur lors du clic souris. L’activation se fera lors du clic sur le bouton searchXY. L'évènement positionné dans la fonction configureAction() activera l'écouteur. La fonction configureAction() est appelée suite à la construction du widget dans l’ihm.
configureAction: function() { ExampleXY.superclass.configureAction.apply(this); if (this.isPortal()) { Y.one("#searchXY").on("click", this.clickOnMapListener, this); } },
Pour désactiver l'écouteur, la fonction surchargée est reinitView(). Cette fonction peut être appelée par l'évènement Y.fire("reinitView") (utilisé notamment par le widget Erase fourni par défaut).
reinitView: function() { this.removeClickOnMapListener(); },
Suite à l’activation de ce widget, chaque clic sur la carte permettra d’afficher les coordonnées x et y de la souris.
setXY: function(x,y) { var map = this.getMap(); Y.one("#x").set("value", x*map.precision); Y.one("#y").set("value", y*map.precision); }
A présent, voici la description complète de ce widget :
YUI.add("geoconcept-widget-example-xy", function(Y) { /* ExampleXY class constructor */ function ExampleXY(config) { ExampleXY.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). */ ExampleXY.NAME = "exampleXY"; Y.extend(ExampleXY, Y.GCUI.WidgetBase, { initializer: function(config) { ExampleXY.superclass.initializer.apply(this); }, destructor: function() { ExampleXY.superclass.destructor.apply(this); }, configureAction: function() { ExampleXY.superclass.configureAction.apply(this); if (this.isPortal()) { Y.one("#searchXY").on("click", this.clickOnMapListener, this); } }, reinitView: function() { this.removeClickOnMapListener(); }, getWidget: function() { var htmlContent = "<div class='xyBG'><div class='xyField'><label>"+this._("widget.exampleXY.x") + "</label></br><input type='text' id='x' size='15' disabled='disabled'/></div>" + "<div class='xyField'><label>" + this._("widget.exampleXY.y") + "</label></br><input type='text' id='y' size='15' disabled='disabled'/></div>" + "<div class='xyButton egw-btn'><button id='searchXY'>" + this._("widget.exampleXY.button") + "</button></div>"; if (!this.isPortal()) { htmlContent = htmlContent + this.getWrapper(); } htmlContent += "</div>"; return ("<div id='"+this.getId()+"'>"+htmlContent+"</div>"); }, clickOnMapListener: function() { var wExampleXY = this; function clickListener(){} clickListener.prototype.onSelect = function(x,y,xpx,ypx){ wExampleXY.setXY(x,y); }; var listener = new clickListener(); DynMapAddMouseSelectionEventListener(this.getMap(), "getXY", listener) }, // Remove create poi listener removeClickOnMapListener: function() { DynMapRemoveListener(this.getMap(),"click","getXY"); }, setXY: function(x,y) { var map = this.getMap(); Y.one("#x").set("value", x*map.precision); Y.one("#y").set("value", y*map.precision); } }); Y.namespace("GCUI"); Y.GCUI.ExampleXY = ExampleXY; }, "1.0.0", {requires: ["project-widgetbase"] });
Dans cet exemple, le widget n'étant pas un bouton, le fichier css doit être enrichit pour personnaliser l’outil. Ci-dessous se trouve une des possibilités d’affichage du widget.
.xyField { padding: 0 10px; float: left; } .xyButton { float: left; padding: 13px 0px 0px; } .xyBG { width: 100%; height: 40px; } .xyField label{ font-weight: bold; }
Voici l’image nécessaire à la réalisation de ce widget.
Voici les clés de base à placer dans le fichier properties :
widget.exampleXY.label=Exemple de coordonn\u00E9es widget.exampleXY.description=R\u00E9cup\u00E9rer les coordonn\u00E9es \u00E0 partir d\'un clic
Pour les autres textes du widget, il est nécessaire d’ajouter les clés suivantes :
widget.exampleXY.button=Activer widget.exampleXY.x=X widget.exampleXY.y=Y
Pour plus d’information sur les ressources, se référer au widget ExampleLegend.