[Commits] r1026 - in sandbox/camptocamp/geobretagne: examples lib lib/GeoExt/widgets lib/GeoExt/widgets/grid tests tests/lib/GeoExt/widgets tests/lib/GeoExt/widgets/grid

commits at geoext.org commits at geoext.org
Tue Jun 9 15:47:12 CEST 2009


Author: fvanderbiest
Date: 2009-06-09 15:47:12 +0200 (Tue, 09 Jun 2009)
New Revision: 1026

Added:
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/grid/
   sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/grid/FeatureSelectionModel.js
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/grid/
   sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html
Modified:
   sandbox/camptocamp/geobretagne/examples/feature-grid.html
   sandbox/camptocamp/geobretagne/examples/feature-grid.js
   sandbox/camptocamp/geobretagne/lib/GeoExt.js
   sandbox/camptocamp/geobretagne/tests/list-tests.html
Log:
added GeoExt.grid.FeatureSelectionModel (geoExt ticket #77)

Modified: sandbox/camptocamp/geobretagne/examples/feature-grid.html
===================================================================
--- sandbox/camptocamp/geobretagne/examples/feature-grid.html	2009-06-09 09:37:16 UTC (rev 1025)
+++ sandbox/camptocamp/geobretagne/examples/feature-grid.html	2009-06-09 13:47:12 UTC (rev 1026)
@@ -18,8 +18,9 @@
         GeoJSON document (data/summits.json).</p>
         
         <p>Because the layer and the store are bound to each other, the
-        features loaded into the store are automatically added to the
-        layer.</p>
+        features loaded into the store are automatically added to the layer. A
+        GeoExt feature selection model is also used so that selecting rows in
+        the grid selects features in the layer, and vice-versa.</p>
 
         <p>See <a href=feature-grid.js>feature-grid.js</a>.</p>
 

Modified: sandbox/camptocamp/geobretagne/examples/feature-grid.js
===================================================================
--- sandbox/camptocamp/geobretagne/examples/feature-grid.js	2009-06-09 09:37:16 UTC (rev 1025)
+++ sandbox/camptocamp/geobretagne/examples/feature-grid.js	2009-06-09 13:47:12 UTC (rev 1026)
@@ -56,7 +56,8 @@
             header: "Elevation",
             width: 100,
             dataIndex: "elevation"
-        }]
+        }],
+        sm: new GeoExt.grid.FeatureSelectionModel()
     });
 
     // create a panel and add the map panel and grid panel

Added: sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/grid/FeatureSelectionModel.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt/widgets/grid/FeatureSelectionModel.js	2009-06-09 13:47:12 UTC (rev 1026)
@@ -0,0 +1,310 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * ¹ pending approval */
+
+/**
+ * @include GeoExt/grid/FeatureSelectionModel.js
+ */
+
+/** api: (define)
+ *  module = GeoExt.grid
+ *  class = RowSelectionModel
+ *  base_link = `Ext.grid.RowSelectionModel <http://extjs.com/deploy/dev/docs/?class=Ext.grid.RowSelectionModel>`_
+ */
+Ext.namespace('GeoExt', 'GeoExt.grid');
+
+GeoExt.grid.FeatureSelectionModelMixin = {
+
+    /** api: config[autoBind]
+     *  ``Boolean``  Whether to bind selection on features and rows at startup. Defaults to true.
+     */
+    autoBind: true,
+
+    /** api: config[selectControl]
+     *  ``OpenLayers.Control.SelectFeature``
+     *  A pre-configured SelectControl with one or several vector layers. If not provided, an empty
+     *  :class:`OpenLayers.Control.SelectFeature` will be created. If a selectControl is passed, 
+     *  any layer or layers config option will be ignored, and its 'multiple' option will be used to
+     *  configure the selectionModel accordingly (unless the singleSelect config option is present).
+     */
+    /** api: property[selectControl]
+     *  ``OpenLayers.Control.SelectFeature``
+     *  This selectControl handles selection of features on vector layers
+     */
+    selectControl: null,
+
+    /** api: config[layers]
+     *  ``Array(OpenLayers.Layer.Vector)``
+     *  An array of vector layers on which features can be selected. 
+     *  If not provided, an empty :class:`OpenLayers.Layer.Vector` will be created.
+     *  If specified, the layer config option will be ignored.
+     */
+    /** api: property[layers]
+     *  ``Array(OpenLayers.Layer.Vector)``
+     *  An array of vector layers on which we handle selection of features. 
+     */
+    layers: null,
+    
+    /** api: config[layer]
+     *  ``OpenLayers.Layer.Vector``
+     *  A vector layer on which features can be selected. 
+     *  If not provided, an empty :class:`OpenLayers.Layer.Vector` will be created.
+     */
+    
+    /** api: property[bound]
+     *  ``Boolean``  Is selection of features and rows bound ?
+     */
+    bound: false,
+    
+    // private
+    _classRef: null,
+    _featureEvents: true,
+    _rowEvents: true,
+    
+    /** private: method[constructor]
+     *  Private constructor override.
+     */
+    constructor: function(config) {
+        config = config || {};
+        if (config.selectControl &&
+            config.selectControl.CLASS_NAME == "OpenLayers.Control.SelectFeature") {
+            this.selectControl = config.selectControl;
+            this.layers = this.selectControl.layers || [this.selectControl.layer];
+            /*
+            Either the user gives a value to singleSelect, or he doesn't.
+            In the first case, the selectControl should be set his 'multiple' config key accordingly
+            In the second case, the selectionModel adapts to the given selectControl multiple option
+            */
+            if (typeof(config.singleSelect) == 'undefined') {
+                this.singleSelect = !this.selectControl.multiple;
+            } else {
+                this.selectControl.multiple = !config.singleSelect;
+            }
+            delete config.selectControl;
+        } else if ((config.layer && config.layer.CLASS_NAME == "OpenLayers.Layer.Vector") 
+                || (config.layers instanceof Array)) 
+        {
+            if (config.layers) {
+                this.layers = config.layers;
+            } else {
+                this.layers = [config.layer];
+                delete config.layer;
+            }
+            this.selectControl = new OpenLayers.Control.SelectFeature(this.layers, {
+                toggle:true
+            });
+            /*
+            Either the user gives a value to singleSelect, or he doesn't.
+            In the first case, the selectControl should be set his 'multiple' config key accordingly
+            In the second case, the selectControl adapts to the SelectionModel's default singleSelect option (which is false)
+            */
+            if (typeof(config.singleSelect) == 'undefined') {
+                this.selectControl.multiple = true; // default value for Ext's selection models
+            } else {
+                this.selectControl.multiple = !config.singleSelect;
+            }
+        }
+        this._classRef = arguments.callee.superclass;
+        this._classRef.constructor.call(this, config);
+    },
+    
+    /** private: method[initEvents]
+     *  Called after this.grid is defined
+     */
+    initEvents: function() {
+        this._classRef.initEvents.call(this);
+        if ((this.grid.getStore().layer) && !(this.selectControl)) {
+            this.layers = [this.grid.getStore().layer];
+            this.selectControl = new OpenLayers.Control.SelectFeature(this.layers, {
+                multiple: !this.singleSelect,
+                toggle: true
+            }); 
+        } else {
+            OpenLayers.Console.error(
+                "no OpenLayers.Control.SelectFeature, "+
+                "nor OpenLayers.Layer.Vector provided, "+
+                "nor bound to a grid with a GeoExt.data.FeatureStore"
+            );
+        }
+        if (this.autoBind) {
+            this.bind();
+        }
+    },
+    
+    /** api: method[bind]
+     *  
+     *  Turn on bidirectional link between row and feature selection
+     */
+    bind: function() {
+        if (!this.bound) {
+            this.featureEventsOn();
+            this.rowEventsOn();
+            this.layers[0].map.addControl(this.selectControl);
+            this.selectControl.activate();
+            this.bound = true;
+            return true;
+        }
+        return false;
+    },
+    
+    /** api: method[unbind]
+     *  
+     *  Turn off bidirectional link between row and feature selection
+     */
+    unbind: function() {
+        if (this.bound) {
+            this.featureEventsOff();
+            this.rowEventsOff();
+            this.selectControl.deactivate();
+            this.layers[0].map.removeControl(this.selectControl);
+            this.bound = false;
+            return true;
+        }
+        return false;
+    },
+    
+    /** private: method[rowEventsOff]
+     *  
+     *  Turn off the row events.
+     */
+    rowEventsOff: function() {
+        this.un("rowselect", this.rowSelected, this);
+        this.un("rowdeselect", this.rowDeselected, this);
+    },
+
+    /** private: method[rowEventsOn]
+     *  
+     *  Turn on the row events.
+     */
+    rowEventsOn: function() {
+        this.on("rowselect", this.rowSelected, this);
+        this.on("rowdeselect", this.rowDeselected, this);
+    },
+
+    /** private: method[featureEventsOff]
+     *  
+     *  Turn off the feature events.
+     */
+    featureEventsOff: function() {
+        var layers = this.layers;
+        for (var i = 0, len = layers.length; i < len; i++) {
+            layers[i].events.un({
+                featureselected: this.featureSelected,
+                featureunselected: this.featureUnselected,
+                scope: this
+            });
+        }
+    },
+
+    /** private: method[featureEventsOn]
+     *  
+     *  Turn on the feature events.
+     */
+    featureEventsOn: function() {
+        var layers = this.layers;
+        for (var i = 0, len = layers.length; i < len; i++) {
+            layers[i].events.on({
+                featureselected: this.featureSelected,
+                featureunselected: this.featureUnselected,
+                scope: this
+            });
+        }
+    },
+    
+    /** private: method[featureSelected]
+     *  :param evt: ``Object`` An object with a feature property referencing
+     *                         the selected feature.
+     */
+    featureSelected: function(evt) {
+        if (!this._featureEvents) {
+            return;
+        }
+        var store = this.grid.store;
+        var row = store.findBy(function(record, id) {
+            return record.data.feature == evt.feature;
+        });
+        if (row != -1 && !this.isSelected(row)) {
+            this._rowEvents = false;
+            this.selectRow(row, !this.singleSelect);
+            this._rowEvents = true;
+            // focus the row in the grid to ensure it is visible
+            this.grid.getView().focusRow(row);
+        }
+    },
+    
+    /** private: method[featureUnselected]
+     *  :param evt: ``Object`` An object with a feature property referencing
+     *                         the unselected feature.
+     */
+    featureUnselected: function(evt) {
+        if (!this._featureEvents) {
+            return;
+        }
+        var store = this.grid.store;
+        var row = store.findBy(function(record, id) {
+            return record.data.feature == evt.feature;
+        });
+        if (row != -1 && this.isSelected(row)) {
+            this._rowEvents = false;
+            this.deselectRow(row); 
+            this._rowEvents = true;
+            this.grid.getView().focusRow(row);
+        }
+    },
+    
+    /** private: method[rowSelected]
+     *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
+     *  :param row: ``Integer`` The row index.
+     *  :param record: ``Ext.data.Record`` The record.
+     */
+    rowSelected: function(model, row, record) {
+        if (!this._rowEvents) {
+            return;
+        }
+        var layers = this.layers;
+        var feature = record.data.feature;
+        if (!feature) {
+            return;
+        }
+        for (var i = 0, len = layers.length; i < len; i++) {
+            if (layers[i].selectedFeatures.indexOf(feature) == -1) {
+                this._featureEvents = false;
+                this.selectControl.select(feature);
+                this._featureEvents = true;
+                break;
+            }
+        }
+    },
+    
+    /** private: method[rowDeselected]
+     *  :param model: ``Ext.grid.RowSelectModel`` The row select model.
+     *  :param row: ``Integer`` The row index.
+     *  :param record: ``Ext.data.Record`` The record.
+     */
+    rowDeselected: function(model, row, record) {
+        if (!this._rowEvents) {
+            return;
+        }
+        var layers = this.layers;
+        var feature = record.data.feature;
+        if (!feature) {
+            return;
+        }
+        for (var i = 0, len = layers.length; i < len; i++) {
+            if (layers[i].selectedFeatures.indexOf(feature) != -1) {
+                this._featureEvents = false;
+                this.selectControl.unselect(feature);
+                this._featureEvents = true;
+                break;
+            }
+        }
+    }
+};
+
+GeoExt.grid.FeatureSelectionModel = Ext.extend(
+    Ext.grid.RowSelectionModel,
+    GeoExt.grid.FeatureSelectionModelMixin
+);
\ No newline at end of file

Modified: sandbox/camptocamp/geobretagne/lib/GeoExt.js
===================================================================
--- sandbox/camptocamp/geobretagne/lib/GeoExt.js	2009-06-09 09:37:16 UTC (rev 1025)
+++ sandbox/camptocamp/geobretagne/lib/GeoExt.js	2009-06-09 13:47:12 UTC (rev 1026)
@@ -82,7 +82,8 @@
             "GeoExt/widgets/tree/OverlayLayerContainer.js",
             "GeoExt/widgets/LegendImage.js",
             "GeoExt/widgets/LegendWMS.js",
-            "GeoExt/widgets/LegendPanel.js"
+            "GeoExt/widgets/LegendPanel.js",
+            "GeoExt/widgets/grid/FeatureSelectionModel.js"
         );
 
         var agent = navigator.userAgent;

Added: sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	                        (rev 0)
+++ sandbox/camptocamp/geobretagne/tests/lib/GeoExt/widgets/grid/FeatureSelectionModel.html	2009-06-09 13:47:12 UTC (rev 1026)
@@ -0,0 +1,294 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+    
+        function test_init(t) {
+            t.plan(8);
+
+            /*
+             * Set up
+             */
+
+            var map, layer, selectControl, store, grid, sm, cm;
+            
+            map = new OpenLayers.Map();
+            layer = new OpenLayers.Layer.Vector("vector");
+            
+            var layers = [layer];
+            map.addLayers(layers);
+
+            /*
+             * Test
+             */
+
+            // 4 tests
+            sm = new GeoExt.grid.FeatureSelectionModel({layers: layers});
+            cm = new Ext.grid.ColumnModel([{header: "name"}]);
+            
+            t.ok(sm instanceof Ext.grid.RowSelectionModel,
+                 "a feature selection model is a row selection model");
+            t.ok(sm.selectControl instanceof OpenLayers.Control.SelectFeature,
+                 "ctor creates a select feature control when passed a layer");
+            
+            store = new GeoExt.data.FeatureStore({layer: layer});
+            
+            grid = new Ext.grid.GridPanel({sm: sm, cm: cm, store: store, renderTo: "grid", width: 100, height: 100});
+
+            t.ok(sm.selectControl.layers == layers,
+                 "ctor configures the select feature control with the passed layers");
+            t.eq(sm.selectControl.multiple, true,
+                 "ctor configures the select feature control with multiple true");
+            
+            grid.view.processRows = function(){}; // hack to prevent this.ds is null, line 32463 ext-all-debug
+            grid.destroy(); 
+
+            // 1 test
+            sm = new GeoExt.grid.FeatureSelectionModel();
+            grid = new Ext.grid.GridPanel({sm: sm, cm: cm, store: store, renderTo: "grid", width: 100, height: 100});
+            t.ok(OpenLayers.Util.indexOf(sm.selectControl.layers, layer) > -1,
+                 "ctor configures the select feature control with the store layer");
+                 
+            grid.view.processRows = function(){};
+            grid.destroy();
+
+            // 1 test
+            sm = new GeoExt.grid.FeatureSelectionModel({singleSelect: true, layer: layer});
+            grid = new Ext.grid.GridPanel({sm: sm, cm: cm, store: store, renderTo: "grid", width: 100, height: 100});
+            t.eq(sm.selectControl.multiple, false,
+                 "ctor configures the select feature control with multiple false");
+                 
+            grid.view.processRows = function(){};
+            grid.destroy();
+
+            // 1 test
+            selectControl = new OpenLayers.Control.SelectFeature(layer);
+            sm = new GeoExt.grid.FeatureSelectionModel({selectControl: selectControl});
+            grid = new Ext.grid.GridPanel({sm: sm, cm: cm, store: store, renderTo: "grid", width: 100, height: 100});
+            t.ok(sm.selectControl == selectControl,
+                 "ctor sets the passed select feature control in the instance");
+                 
+            grid.view.processRows = function(){};
+            grid.destroy();
+
+            // 1 test
+            
+            CheckboxSelectionModel = Ext.extend(
+                Ext.grid.CheckboxSelectionModel,
+                GeoExt.grid.FeatureSelectionModelMixin
+            );
+            sm = new CheckboxSelectionModel({layer: layer});
+            grid = new Ext.grid.GridPanel({sm: sm, cm: cm, store: store, renderTo: "grid", width: 100, height: 100});
+            t.ok(sm instanceof Ext.grid.CheckboxSelectionModel,
+                 "instance is a checkbox selection model");
+                 
+            grid.view.processRows = function(){};
+            grid.destroy();
+        }
+
+        function test_row_selection(t) {
+            t.plan(7);
+
+            /*
+             * Set up
+             */
+
+            var map, layer, features, store, sm, grid, e;
+
+            map = new OpenLayers.Map('map');
+
+            layer = new OpenLayers.Layer.Vector("vector");
+            map.addLayer(layer);
+
+            features = [
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo1", bar: "bar1"}
+                ),
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo2", bar: "bar2"}
+                )
+            ];
+            
+            store = new GeoExt.data.FeatureStore({
+                layer: layer,
+                data: features
+            });
+
+            sm = new GeoExt.grid.FeatureSelectionModel();
+
+            grid = new Ext.grid.GridPanel({
+                renderTo: "grid",
+                store: store,
+                width: 100,
+                height: 100,
+                columns: [{
+                    dataIndex: "foo"
+                }, {
+                    dataIndex: "bar"
+                }],
+                sm: sm,
+                cm: new Ext.grid.ColumnModel([{dataIndex: "foo"}, {dataIndex: "bar"}])
+            });
+   
+            // hack required because of some weird problem with the grid view
+            grid.view.focusRow = function() {};
+            
+            /*
+             * Test
+             */
+
+            // simulate a mousedown on the first row
+            // test that the first feature is selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) > -1,
+                 "click on row 0 selects feature 0");
+                 
+            // simulate a mousedown on the first row
+            // test that the first feature is deselected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: true
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) < 0,
+                 "click on row 0 deselects feature 0");
+
+            // simulate a mousedown on the second row
+            grid.fireEvent("rowmousedown", grid, 1, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[1]) > -1,
+                 "click on row 1 selects feature 1");
+
+            sm.clearSelections();
+
+            // select feature 0
+            // test that the first row is selected
+            sm.selectControl.select(features[0]);
+            t.ok(sm.isSelected(0),
+                 "selecting feature 0 selects row 0");
+
+            // select feature 1
+            // test that the second row is selected
+            sm.selectControl.select(features[1]);
+            t.ok(sm.isSelected(1),
+                 "selecting feature 1 selects row 1");
+
+            // unselect feature 0
+            // test that the first row is selected
+            sm.selectControl.unselect(features[0]);
+            t.ok(!sm.isSelected(0),
+                 "unselecting feature 0 unselects row 0");
+
+            // unselect feature 1
+            // test that the second row is unselected
+            sm.selectControl.unselect(features[1]);
+            t.ok(!sm.isSelected(1),
+                 "unselecting feature 1 unselects row 1");
+            
+            /*
+             * Tear down
+             */
+            grid.view.renderRows = function(){}; // hack to prevent error on grid destroy
+            grid.view.processRows = function(){}; // hack
+            grid.destroy();
+        }
+        
+        
+        function test_autoBind(t) {
+            t.plan(2);
+
+            /*
+             * Set up
+             */
+
+            var map, layer, features, store, sm, grid, e;
+
+            map = new OpenLayers.Map('map');
+
+            layer = new OpenLayers.Layer.Vector("vector");
+            map.addLayer(layer);
+
+            features = [
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo1", bar: "bar1"}
+                ),
+                new OpenLayers.Feature.Vector(null,
+                    {foo: "foo2", bar: "bar2"}
+                )
+            ];
+            
+            store = new GeoExt.data.FeatureStore({
+                layer: layer,
+                data: features
+            });
+
+            sm = new GeoExt.grid.FeatureSelectionModel({
+                autoBind: false
+            });
+
+            grid = new Ext.grid.GridPanel({
+                renderTo: "grid",
+                store: store,
+                width: 100,
+                height: 100,
+                columns: [{
+                    dataIndex: "foo"
+                }, {
+                    dataIndex: "bar"
+                }],
+                sm: sm,
+                cm: new Ext.grid.ColumnModel([{dataIndex: "foo"}, {dataIndex: "bar"}])
+            });
+   
+            // hack required because of some problem with the grid view
+            grid.view.focusRow = function() {};
+            
+            /*
+             * Test
+             */
+
+            // simulate a mousedown on the first row
+            // test that the first feature is selected in the layer
+            e = {
+                button: 0,
+                shiftKey: false,
+                ctrlKey: false
+            };
+            grid.fireEvent("rowmousedown", grid, 0, e);
+            t.ok(OpenLayers.Util.indexOf(layer.selectedFeatures,
+                                         features[0]) < 0,
+                 "click on row 0 does not select feature 0");
+            
+            // select feature 1
+            // test that the second row is not selected
+            sm.selectControl.select(features[1]);
+            t.ok(!sm.isSelected(1),
+                 "selecting feature 1 does not select row 1");
+                 
+            /*
+             * Tear down
+             */
+            grid.view.renderRows = function(){}; // hack to prevent error on grid destroy
+            grid.view.processRows = function(){}; // hack
+            grid.destroy();
+        }
+    </script>
+
+  <body>
+      <div id="map" style="width:100px;height:100px"></div>
+      <div id="grid"></div>
+  </body>
+</html>

Modified: sandbox/camptocamp/geobretagne/tests/list-tests.html
===================================================================
--- sandbox/camptocamp/geobretagne/tests/list-tests.html	2009-06-09 09:37:16 UTC (rev 1025)
+++ sandbox/camptocamp/geobretagne/tests/list-tests.html	2009-06-09 13:47:12 UTC (rev 1026)
@@ -19,4 +19,5 @@
   <li>lib/GeoExt/widgets/tree/LayerNode.html</li>
   <li>lib/GeoExt/widgets/tree/LayerContainer.html</li>
   <li>lib/GeoExt/widgets/LegendPanel.html</li>
+  <li>lib/GeoExt/widgets/grid/FeatureSelectionModel.html</li>
 </ul>



More information about the Commits mailing list