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

 

 

ExtJS 5/6 : Right Click/Context Menu in a Grid/Tree Panel

Recently, I added a “contextmenu” to my tree panel (same stuff form grid panel in fact), especially on item right click.

The implementation is very easy : just add a “itemcontextmenu” listener

Example:

{
    // my panel manages some layers on a map
    xtype: 'treepanel'
    ,region: 'west'
    ,title: 'Layers' // 
[...]

    ,listeners: {
        scope: me
       ,itemcontextmenu: function(tree, record, item, index, e, eOpts ) {
          // Optimize : create menu once
          var menu_grid = new Ext.menu.Menu({ items:
            [
                { text: 'More details', handler: function() {console.log("More details");} },
                { text: 'Delete', handler: function() {console.log("Delete");} }
            ]
            });
          var position = e.getXY();
          e.stopEvent();
          menu_grid.showAt(position);
       }
   }
[...]
}

And… it works. But, there is ONE issue: when used on a “desktop” webapp, the context menu isn’t always hidden when use clicks outside. It happens only when the mouse never hovers the context menu.

To solve this issue very easily, I change the contextmenu show position

{
    // my panel manages some layers on a map
    xtype: 'treepanel'
    ,region: 'west'
    ,title: 'Layers' // 
[...]

    ,listeners: {
        scope: me
       ,itemcontextmenu: function(tree, record, item, index, e, eOpts ) {
          // Optimize : create menu once
          var menu_grid = new Ext.menu.Menu({ items:
            [
                { text: 'More details', handler: function() {console.log("More details");} },
                { text: 'Delete', handler: function() {console.log("Delete");} }
            ]
            });
          // HERE IS THE MAIN CHANGE
          var position = [e.getX()-10, e.getY()-10];
          e.stopEvent();
          menu_grid.showAt(position);
       }
   }
[...]
}