ExtJS5 and Leaflet : Getting Started. Example with a “Desktop” window

Here is couple of lines on using Leaflet with ExtJS. The Leaflet widget I’ve been using is based on a GitHub project by  kazmiekr (@see Leaflet UX). Many thanks to that !!!

Setup

The setup is VERY easy. The only requirements are to add in index.html all the leaflet common requirements.

https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css
src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.js

The Leaflet Map Widget

As I wrote it above, the code is widely based on kazmiekr work (@see Leaflet UX). Couple of improvements:

  • Multiple tiles providers support,
  • Use of OpenStreetMap instead of CloudMade as default map,
  • initMap function, called at the end of “afterrender” listener.
Ext.define('Ext.ux.LeafletMapView', {
    extend: 'Ext.Component'
    ,alias: 'widget.leafletmapview'

/* // Optional: add an additionnal controller 
   // with alias: controller.leaflet-map
,requires: [
 'Ext.ux.LeafletMapController'
 ]
   ,controller: 'leaflet-map'
*/
    ,config:{
        initialLocation: null,
        initialZoomLevel: null,
        map: null,
        layerControl: null,
        useCurrentLocation: false

// Description of tiles providers
        ,tiles: {
            osm: {
                title: 'OSM'
                ,url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
                ,maxZoom: 18
                ,attribution: "OpenStreetmap"
            }
            ,mapbox : {
                title: 'MapBox v4'
                ,url: 'http://{s}.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token={key}'
                ,maxZoom: 18
                ,key: "MAPBOX API KEY"
                ,attribution: '© GGL/OSM'
            }
        }
        ,defaultTile: 'osm' // default tile provider
        ,initMap: function(map) {} // called afterRender
    }


// Will be called by afterrender => add tiles selector
    ,initTiles: function(map) {
        var tiles = {}, list = this.getTiles();
        for(var i in list) {
            var t = list[i]
                ,tile = L.tileLayer(t.url, t);
            tiles[t.title] = tile;
            if (i == this.getDefaultTile()) // Add Default map..
                tile.addTo(map);
        }

        // Add Map Control
        var layerControl = L.control.layers(tiles, {});
        layerControl.addTo(map);
        this.layerControl = layerControl;
    }

// afterrender is called the 1st time the widget is created
    ,afterRender: function(t, eOpts){
        this.callParent(arguments); 
        var leafletRef = window.L;
        if (leafletRef == null){
            this.update("No leaflet library loaded");
        } else {
            var map = L.map(this.getId());
            this.setMap(map);
            this.initTiles(map);

            var initialLocation = this.getInitialLocation();
            var initialZoomLevel = this.getInitialZoomLevel();
            if (initialLocation && initialZoomLevel){
                map.setView(initialLocation, initialZoomLevel);
            } else {
                map.fitWorld();
            }
            if (this.getUseCurrentLocation() == true){
                map.locate({
                    setView: true
                });
            }

            if (Ext.isFunction(this.initMap)) {
                this.initMap(Ext.isDefined(this.scope) ? this.scope : this, map);
            }
        }
    }

    /** Called on Element resize => invalidate map **/
    ,onResize: function(w, h, oW, oH){
        this.callParent(arguments);
        var map = this.getMap();
        if (map) map.invalidateSize();
    }
});

Example : a Desktop window (with a Tree Panel on left)

Ext.define('Desktop.map.GlobalMapWindow', {
    extend: 'Ext.ux.desktop.Module',
    requires: [
        'Ext.panel.Panel'
        ,'Ext.ux.LeafletMapView'
        ,'Ext.tree.Panel'
    ]

    ,id: 'panel-map-global'
    ,title: "Window Title"
    ,iconCls: 'fa fa-globe' // font-awesome icon

    ,init : function(){
        this.launcher = { // start menu shortcut
            text: this.title
            ,iconCls: this.iconCls
        }
    }

    ,config : {
        mapWidget: null
        ,treeWidget : null
    }
/**
 * Create the Window
 * @returns {*}
 */
,createWindow : function(){
    //GLC.Config.log.log(this.id, 'createWindow');
    var me = this,
        app = this.app
        ,desktop = app.getDesktop()
        ,win = desktop.getWindow( this.id );
    if(!win || win.isDestroyed){
        // reset internal object cache
        this.mapWidget = null;
        this.treeWidget = null;
        // create window
        win = desktop.createWindow({
            id: this.id,
            title: this.title,
            width: 740,
            height: 480,
            iconCls: this.iconCls,
            animCollapse: false,
            border: false,
            constrainHeader: true,
            layout: {
                type: 'border'
            }
   ,items: [
    {  // Left Tree Panel
        xtype: 'treepanel'
        ,region: 'west'
        ,title: null
        ,itemId: 'treelayers'
        ,rootVisible: true
        ,split: true
        ,collapsible: true
        ,collapsed: false
        ,width: 200
        //,fields: ['name', 'description']
        ///*
        ,columns: []
        ,listeners: { }
    } // eo TreePanel

   ,{ // The Map
                    xtype: 'leafletmapview'
                    ,region: 'center'
                    ,flex:2
                    ,useCurrentLocation: true
                    ,itemId: 'map'
                    /* // in case I need an additionnal
                       //afterrender function
                    ,initMap: me.initMap
                    ,scope: me
                    */
                }
            ]
        });
    }
    //console.log(win);
    return win;
}

/**
 * Get the Leaflet.Map component with LeafletMap Widget
 * @returns Leaflet.Map
 */
,getMap: function() { // retrieve the map
    if (!this.mapWidget) this.mapWidget = Ext.ComponentQuery.query("#" + this.id + " > #map")[0];
    return this.mapWidget.getMap();
}

/**
 * Get the PanelTree component
 * @returns Ext.tree.Panel
 */
,getTreeWidget: function() {
    if (!this.treeWidget) this.treeWidget = Ext.ComponentQuery.query("#" + this.id + " > #treelayers")[0];
    return this.treeWidget;
}

}); // eo Desktop.map.GlobalMapWindow

 

 

AngularJS & Leaflet directive: how to allow user to resize the map

I was recently working with Leaflet and Angular. While Leaflet? just to avoid (again and again) Google Maps.

I do recommend Leaftet directive for Angular. Very efficient, quite powerful. However, it lacks one feature : redim the height of its map dynamically, and thus, save it to the local storage.

So I have written a very small and efficient directive on top of leaflet directive for Angular that could be found here.

To use it, add the script after leaflet ones

<script src="angular-leaflet-directive-mapresize.js"></script>

and add the “dragmap” directive to leaflet’one:

<leaflet width='100%' height='600' defaults="defaults" dragmap></leaflet>

It adds a very basic div below the map that could be use to resize the height of the map.
The most important lines into the module’s code are the following:

   controller.getMap().then(function(map) {
     map.invalidateSize();
   });

It forces leaflet to recalculate the size of the div and avoids some gray tiles.