[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