In this page you'll find sort of a tutorial to learn how to create your own MapFish client widget. You'll also learn how to build it so that it can be lazy rendered by ExtJS and easy to integrate into a complex layout if needed.

Before You Start

This turorial assumes that you have already installed MapFish. If not, go to the installation page and follow the instructions. Once done, you can read on.

It also assumes that you already know how to use a MapFish widget in your code. Please refer to this tutorial if it isn't the case.

Widget constructor

In this tutorial, we're going to build a widget that creates an unordered list of cities the user will be able to center the map to.

Let's start with the javascript code. Create a new javascript file named "Locations.js" (this is an arbitrary name) as follows:

/**
 * @requires OpenLayers/Map.js
 */

Ext.namespace('mapfish.widgets');

mapfish.widgets.Locations = function(config) {
    Ext.apply(this, config);
    mapfish.widgets.Locations.superclass.constructor.call(this);
}

Ext.extend(mapfish.widgets.Locations, Ext.Container, {
    // code is to be added here
});

First, we put some @requires annotations. Those annotations are needed if you intend to use the MapFish JavaScript build script. In our case, OpenLayers.Map class is required because we'll use the zoomTo() method.

Then we define the mapfish.widgets.Locations constructor. The constructor calls Ext.apply() so that every configuration option become a property of the object. It also calls its parent's constructor.

Ext.extend() is used to define our mapfish.widgets.Locations class. mapfish.widgets.Locations inherits from Ext.Container. Inheriting from Ext.Container is the key to being able to add our widgets into Ext layout. The code of the class is located in the third argument of Ext.extend().

HTML sample

Let's create a new "locations_simple.html" file with the following code:

<html>
<head>
  <title>MapFish Tutorial - How To Create Widgets</title>
  <link rel="stylesheet" type="text/css" href="/path/to/mfbase/ext/resources/css/ext-all.css" />
  
  <script type="text/javascript" src="/path/to/mfbase/openlayers/lib/OpenLayers.js"></script>
  <script type="text/javascript" src="/path/to/mfbase/ext/adapter/ext/ext-base.js"></script>
  <script type="text/javascript" src="/path/to/mfbase/ext/ext-all-debug.js"></script>
  <script type="text/javascript" src="/path/to/mfbase/mapfish/MapFish.js"></script>
  <script type="text/javascript" src="Locations.js"></script>
  
  <script type="text/javascript">
    Ext.onReady(function() {
        var map = new OpenLayers.Map($('olmap'));

        var wms = new OpenLayers.Layer.WMS("OpenLayers WMS", 
            "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'}, {buffer: 0});

        map.addLayers([wms]);
        map.addControl(new OpenLayers.Control.LayerSwitcher());
        map.zoomToMaxExtent();
        
        var data = [['Paris', new OpenLayers.LonLat(2.3333333, 48.8666667), 6],
                    ['Buenos Aires', new OpenLayers.LonLat(-58.6725, -34.5875), 6],
                    ['Amsterdam', new OpenLayers.LonLat(4.9166667, 52.35), 6],
                    ['Dakar', new OpenLayers.LonLat(-17.4380556, 14.6708333), 6]];
        
        var locations = new mapfish.widgets.Locations({
            map: map,
            el: 'myDiv',
            data: data
        });
        locations.render();
    });
  </script>
</head>
<body>
  <span></span>
  <div id="myDiv"></div>
  <div id="olmap" style="width:450px;height:300px;background-color:#999;"></div>
 </body>
</html>

What we need to do now is go back to our widget and generate an HTML unordered list with clickable items based on the data passed in the data option. In this example, the data is stored in an array; in general it would be preferable to use the Ext SimpleStore class.

Widget Initialization

So go back to the javascript widget code and add the following:

    // private
    onRender: function(container, position) {
        mapfish.widgets.Locations.superclass.onRender.apply(this, arguments);
        
        // do something here
        var ul = document.createElement("ul");
        for (var i = 0; i < this.data.length; i++) {
            var li = document.createElement("li");
            var link = document.createElement("a");
            link.href = "javascript:void(0)";
            var text = document.createTextNode(this.data[i][0]);
            link.appendChild(text);
            li.appendChild(link);
            ul.appendChild(li);

            Ext.EventManager.on(link, 'click',
                function(evt, el, options){
                    this.map.setCenter(options.location, options.zoomLevel);
                },
                this,
                {location: this.data[i][1], zoomLevel: this.data[i][2]}
            );
        }
        this.el.dom.appendChild(ul);
    }

The onRender() method is a callback method triggered in the Ext.Container extended classes when the render method is called on an object of that class or when this object is lazy rendered as an item of another layout element.

In this code, we first call the superclass's onRender() method. Then we build the unordered list with the several items depending on data. We also register an observer on the link "click" event which will call the map setCenter() method.

Try it in your browser.

Widget For a Complex Layout

What we want to achieve now is to get this working in a complex layout.

First we add the following code at the begining of the onRender() method in the Locations class :

        if (!this.el) {
            this.el = document.createElement('div');
        }

We need this snippet of code because in complex layouts, we usually don't really have an existing DOM element to render the widget to. It's often lazy rendered. In those cases, we don't give any "el" option parameter.

Now let's create a new locations_layout.html file as a copy of the simple one with this javascript code :

    Ext.onReady(function() {
        var map = new OpenLayers.Map($('olmap'));

        var wms = new OpenLayers.Layer.WMS("OpenLayers WMS", 
            "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'}, {buffer: 0});

        map.addLayers([wms]);
        map.addControl(new OpenLayers.Control.LayerSwitcher());
        map.zoomToMaxExtent();
        
        var data = [['Paris', new OpenLayers.LonLat(2.3333333, 48.8666667), 6],
                    ['Buenos Aires', new OpenLayers.LonLat(-58.6725, -34.5875), 6],
                    ['Amsterdam', new OpenLayers.LonLat(4.9166667, 52.35), 6],
                    ['Dakar', new OpenLayers.LonLat(-17.4380556, 14.6708333), 6]];
        
        var locations = new mapfish.widgets.Locations({
            map: map,
            data: data
        });
        
        var mapcomponent = new mapfish.widgets.MapComponent({map: map});
       
        var window = new Ext.Window({
            title: 'Map',
            width: 500,
            height:300,
            minWidth: 300,
            minHeight: 200,
            layout: 'fit',
            plain:true,
            bodyStyle:'padding:5px;',
            items: mapcomponent
        });
        window.show();
        
        var window = new Ext.Window({
            title: 'Cities',
            width: 200,
            height:150,
            minWidth: 200,
            minHeight: 100,
            layout: 'fit',
            plain:true,
            bodyStyle:'padding:5px;',
            items: locations
        });
        window.setPagePosition(20, 40);
        window.show();
    });

What is important to notice :

  • we create a MapComponent that we use as an item for the first window,
  • we removed the "el" option parameter from the locations widget,
  • the locations widget is used as a item for the second window.

Refresh your page in the browser and enjoy!

Attachments