How To Create A Widget
Introduction
This HowTo describe how create a widget using Fusion and MapServer. It will show you all the steps to do and files to create for having functionnal widget. It assume that you have followed the MapServer tutorial and that your Fusion installation is working. In this HowTo, we'll create a widget that display a search form and make a simple search via the MapServer CGI.
HowTo Prerequisites
This HowTo uses the same MapServer data than the MapServerTutorial.
- Fusion: http://trac.osgeo.org/fusion/wiki/GetIt
- MapServer gmap-ms46 sample data: http://dl.maptools.org/dl/
- Icons
You can follow these installation instructions for install the data if you haven't followed the MapServerTutorial but you should follow the entire tutorial and make it works first because this HowTo is a sequel to it.
Basic steps
This is the basic steps that you'll need to do for creating a Fusion widget.
- Create the JavaScript implementation for the widget. This will define all the widget functionnalities available.
- Create the script that the widget will use. (In this HowTo, it will be a PHP script) This step is optionnal. A widget may not needs to use script. (ie. Zoom widget: it doesn't uses a PHP script, it just use a OpenLayers control)
- Add the new widget in a Fusion application.
- Create the WidgetInfo xml of the new widget. This step is also optionnal. It's for the widget documentation. It will describe the widget description and it parameters that you can pass to it through the ApplicationDefinition.xml. (I didn't make it for this tutorial)
Creating the JavaScript implementation
Here, we'll make the JavaScript implementation of the widget. This widget has two visual component: a text box for let the user write the search pattern and a button for execute the search. Fusion already provides a basic button implementation (Fusion.Tool.ButtonBase). So we'll only need to generate the text box manually and then make our widget inherit from the ButtonBase class to have our button. Once we inherit from the ButtonBase, we can implement the button action in the execute method. Take a look to the widget implementation and the comments. This is the content of the file named MySearchWidget.js and have to be in the fusion/widgets/ directory.
/******************************************************************** * Class: Fusion.Widget.MySearchWidget * * A widget example that displays a basic search form. * * **********************************************************************/ Fusion.Widget.MySearchWidget = Class.create(); // This is the way for create a new class Fusion.Widget.MySearchWidget.prototype = { oTextBox : '', sResultPanel : '', // These variables are the data member of our widget sLayerName : '', sAttribute: '', /* This method is off course required. It's the constructor of the widget. All class must have a constructor. When ApplicationDefinition will be parsed, the widgets will be created and initialized with this method. The parameter widgetTag is the json object that contains the widget parameters. (ie. Name, Type, ImageUrl, Label, etc.) */ initialize : function(widgetTag){ // A Fusion widget must implement the base widget class to work. Object.inheritFrom(this,Fusion.Widget.prototype, [widgetTag, true]); this.enable = Fusion.Widget.MySearchWidget.prototype.enable; /* This is to get the specific parameters of the current widget. * The widget allow to set the following parameters in the * ApplicationDefinition.xml: * LayerName: The layer name to execute the mapserver queryByAttributes() * Attribute: The attribute name (column name) * ResultPanel: The place to display the search results. */ var json = widgetTag.extension; this.sLayerName = json.LayerName ? json.LayerName : ''; this.sAttribute = json.Attribute ? json.Attribute : ''; this.sResultPanel = json.ResultPanel ? json.ResultPanel : ''; // Creating the text box html element and adding it to the page. this.oTextBox = document.createElement('input'); this.oTextBox.type = 'text'; this.oTextBox.size = 20; this.oTextBox.className = "searchInputBox"; this.domObj.appendChild(this.oTextBox); // Creating the button with all necessary event binded. Object.inheritFrom(this, Fusion.Tool.ButtonBase.prototype, []); }, /* This method is also important and required. It define the action to do * when we execute the widget. The Fusion.Tool.ButtonBase class has already binded * it to the button click event. In this example, the widget send a ajaxRequest to a * php that use the MapServer PHP/MapScript for doing a queryByAttributes(). */ execute : function() { this.oMap._addWorker(); var options = {}; var pattern = this.oTextBox.value.trim(); if (pattern == "") return; options.sessionid = this.getMap().getSessionID(); options.attribute = this.sAttribute; options.pattern = "/"+pattern+"/i"; options.layer = this.sLayerName; var sl = Fusion.getScriptLanguage(); var loadmapScript = this.oMap.aMaps[0].arch + '/' + sl + '/SimpleSearch.' + sl; var params = 'mapname='+this.oMap.aMaps[0]._sMapname+"&session="+options.sessionid+'&layer='+options.layer+'&attribute='+options.attribute+'&pattern='+options.pattern; // The ajaxRequest is binded to the processQueryResults method of our widget for process the result of the php script. var requestOptions = {onSuccess: this.processQueryResults.bind(this), parameters: params}; Fusion.ajaxRequest(loadmapScript, requestOptions); }, /* This method is specific to our widget and will be called after each ajaxRequest * made for process the results. In this case, i only take the name of cities found and * display them in the result panel. */ processQueryResults : function(r) { this.oMap._removeWorker(); var myDiv = document.getElementById(this.sResultPanel); var o; eval("o="+r.responseText); if (myDiv) { myDiv.innerHTML = ''; if (!o.hasSelection) { myDiv.innerHTML = "no result"; } else { var numResult = o.values.length; for (i = 0; i < numResult; i++) myDiv.innerHTML += o.values[i][5] + "<br/>"; } } }, };
The JavaScript implementation is now done. We'll now need to create the script.
Creating the Script
This script will be used by our widget using an ajax request. In the future, Fusion will probably support more than one script langage, but for the moment it's PHP that is implemented and used. This is my simple script that make a queryByAttributes() to MapServer and returns the results in JSON. This is the content of the file SimpleSearch.php and have to be in the fusion/MapServer/php directory.
<?php /***************************************************************************** * Purpose: create a query by attributes *****************************************************************************/ /* set up the session */ include ("Common.php"); include ("Utilities.php"); include('../../common/php/Utilities.php'); if ( ($_REQUEST['layer'] != '') && ($_REQUEST['attribute'] != '') && ($_REQUEST['pattern'] != '')) { $szLayer = $_REQUEST['layer']; $szAttribute = $_REQUEST['attribute']; $szPattern = $_REQUEST['pattern']; } else { die('layer, attribute or value not set'); } if (!isset($mapName)) { die('mapname not set'); } if (isset($_SESSION['maps']) && isset($_SESSION['maps'][$mapName])) { $oMap = ms_newMapObj($_SESSION['maps'][$mapName]); } $result = NULL; $result->hasSelection = false; $result->values = array(); $oLayer = $oMap->GetLayerByName($szLayer); if (@$oLayer->queryByAttributes($szAttribute,$szPattern, MS_MULTIPLE) == MS_SUCCESS) { $numResults = $oLayer->getNumResults(); $result->hasSelection = true; } header('Content-type: text/x-json'); header('X-JSON: true'); if ($result->hasSelection) { $oLayer->open(); //get first shape to get the attributes $oResultSet = $oLayer->getResult(0); $selFields = array(); $oShape = $oLayer->getShape($oResultSet->tileindex,$oResultSet->shapeindex); while ( list($key,$val) = each($oShape->values) ) { array_push($selFields, $key); } for ($iRes=0; $iRes < $numResults; $iRes++) { $oResultSet = $oLayer->getResult($iRes); $oShape = $oLayer->getShape($oResultSet->tileindex,$oResultSet->shapeindex); $result->values[$iRes] = array(); //field values $iNumField = count($selFields); for($iField=0; $iField < $iNumField; $iField++) { $value = $oShape->values[$selFields[$iField]]; $value = str_replace("'", "\'", $value); array_push($result->values[$iRes],$value); } } $oLayer->close(); } echo var2json($result); ?>
The widget is now complete and needs to be tested.
Adding the new widget in a Fusion application
- Html page
For those who have already the index.html from the MapServer tutorial, you can simply add two <div> somewhere in the page (one for the search widget, and one for the result panel) or replace the old index.html by this one:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Fusion MapServer Widget</title> <script type="text/javascript" src="../fusion/lib/fusion.js"></script> <style type="text/css"> @import url(../fusion/jx/css/jxskin-graphic.css); #bodyContent { position:relative; background-color:#dfe9f5; border: thin solid #000000; height:580px; width:760px; margin-top:25px;} #Map { position:absolute; top:35px; right:5px; width:439px; height:349px; border: thin solid #000000; float: right; visibility: visible; display:block; } #Legend { position:absolute; top:35px; left:5px; width:300px; height:349px; background-color:#ffffff; border: thin solid #000000; overflow-y: auto; overflow-x:hidden; } #toolbar { position:absolute; left: 310px; } #mySimpleSearch { position:absolute; left: 10px; bottom: 150px; } .searchInputBox {float:left;} #myResultPanel { position: absolute; right: 5px; bottom: 15px; height: 170px; width: 439px; color:red; background-color:white; overflow:auto; border: thin solid black; text-align: left;} </style> <script type="text/javascript"> window.onload = function() { Fusion.initialize(); } </script> </head> <body> <center> <div id="bodyContent"> <div id="toolbar"> <div id="Pan" ></div> <div id="ZoomIn"></div> <div id="ZoomOut"></div> <div id="Measure"></div> </div> <div id="Legend" align="left"></div> <div id="Map"></div> <div id="mySimpleSearch"></div> <div id="myResultPanel"></div> </div> </center> </body> </html>
Now, add the new widget definition in the ApplicationDefinition.xml. As you can see, the tag <Extension> is used to add the specifics parameters to our new widget. This widget configuration will uses the layer named popplace for search all the city that matches with the pattern provided in the textbox. Then, the results will be displayed in the myResultPanel div.
<Widget xsi:type="UiWidgetType"> <Name>mySimpleSearch</Name> <Type>MySearchWidget</Type> <StatusItem>A simple search widget</StatusItem> <ImageUrl></ImageUrl> <ImageClass/> <Tooltip></Tooltip> <Label>Search</Label> <Disabled/> <Extension xsi:type="CustomContentType"> <LayerName>popplace</LayerName> <Attribute>NAME</Attribute> <ResultPanel>myResultPanel</ResultPanel> </Extension> </Widget>
You should now be able to test the new widget via the web address. In my case: http://localhost:8080/demo/index.html