/* * Copyright (c) 2008-2015 The Open Source Geospatial Foundation * * Published under the BSD license. * See https://github.com/geoext/geoext2/blob/master/license.txt for the full * text of the license. */ /* * @include OpenLayers/Control/SelectFeature.js * @include OpenLayers/Layer/Vector.js * @include OpenLayers/Util.js * @include OpenLayers/BaseTypes/Class.js * @requires GeoExt/Version.js */ /** * A row selection model which enables automatic selection of features * in the map when rows are selected in the grid and vice-versa. * * Sample code to create a feature grid with a feature selection model: * * Example: * * var gridPanel = Ext.create('Ext.grid.GridPanel', { * title: "Feature Grid", * region: "east", * store: store, * width: 320, * columns: [{ * header: "Name", * width: 200, * dataIndex: "name" * }, { * header: "Elevation", * width: 100, * dataIndex: "elevation" * }], * selType: 'featuremodel' * }); * * @class GeoExt.selection.FeatureModel */ Ext.define('GeoExt.selection.FeatureModel', { extend: 'Ext.selection.RowModel', alias: 'selection.featuremodel', requires: [ 'GeoExt.Version' ], /** * If true the select feature control is activated and deactivated when * binding and unbinding. * * @cfg {Boolean} */ autoActivateControl: true, /** * If true, and if the constructor is passed neither a layer nor a select * feature control, a select feature control is created using the layer * found in the grid's store. Set it to false if you want to manually bind * the selection model to a layer. * * @cfg {Boolean} */ layerFromStore: true, /** * The select feature control instance. If not provided one will be created. * * If provided any "layer" config option will be ignored, and its "multiple" * option will be used to configure the selectionModel. If an `Object` * is provided here, it will be passed as config to the SelectFeature * constructor, and the "layer" config option will be used for the layer. * * @cfg {OpenLayers.Control.SelectFeature} */ selectControl: null, /** * The vector layer used for the creation of the select feature control, it * must already be added to the map. If not provided, the layer bound to the * grid's store, if any, will be used. * * @cfg {OpenLayers.Layer.Vector} layer */ /** * Flag indicating if the selection model is bound. * * @property {Boolean} * @private */ bound: false, /** * An array to store the selected features. * * @property {OpenLayers.Feature.Vector[]} * @private */ selectedFeatures: [], /** * If true the map will recenter on feature selection so that the selected * features are visible. * * @cfg {Boolean} */ autoPanMapOnSelection: false, /** * @private */ constructor: function(config) { config = config || {}; if (config.selectControl instanceof OpenLayers.Control.SelectFeature) { if (!config.singleSelect) { var ctrl = config.selectControl; config.singleSelect = !(ctrl.multiple || !!ctrl.multipleKey); } } else if (config.layer instanceof OpenLayers.Layer.Vector) { this.selectControl = this.createSelectControl( config.layer, config.selectControl); delete config.layer; delete config.selectControl; } if (config.autoPanMapOnSelection) { this.autoPanMapOnSelection = true; delete config.autoPanMapOnSelection; } this.callParent(arguments); }, /** * Called after this.grid is defined. * * @private */ bindComponent: function() { var me = this; me.callParent(arguments); if (me.layerFromStore) { var view = me.view || me.views[0], viewStore = view.getStore(), ctrl = me.selectControl, isSelCtrl = (ctrl instanceof OpenLayers.Control.SelectFeature), layer; if (viewStore) { layer = viewStore.layer; } if (layer && !isSelCtrl) { me.selectControl = me.createSelectControl(layer, ctrl); } } if (me.selectControl) { me.bindLayer(me.selectControl); } }, /** * Create the select feature control. * * @param {OpenLayers.Layer.Vector} layer The vector layer. * @param {Object} config The select feature control config. * @private */ createSelectControl: function(layer, config) { config = config || {}; var singleSelect = config.singleSelect !== undefined ? config.singleSelect : this.singleSelect; config = OpenLayers.Util.extend({ toggle: true, multipleKey: singleSelect ? null : (Ext.isMac ? "metaKey" : "ctrlKey") }, config); var selectControl = new OpenLayers.Control.SelectFeature( layer, config); layer.map.addControl(selectControl); return selectControl; }, /** * Bind the selection model to a layer or a SelectFeature control. * * @param {OpenLayers.Layer.Vector/OpenLayers.Control.SelectFeature} obj * The object this selection model should be bound to, either a vector * layer or a select feature control. * @param {Object} options An object with a "controlConfig" property * referencing the configuration object to pass to the * `OpenLayers.Control.SelectFeature` constructor. * @return {OpenLayers.Control.SelectFeature} The select feature control * this selection model uses. */ bindLayer: function(obj, options) { if (!this.bound) { options = options || {}; this.selectControl = obj; if (obj instanceof OpenLayers.Layer.Vector) { this.selectControl = this.createSelectControl( obj, options.controlConfig ); } if (this.autoActivateControl) { this.selectControl.activate(); } var layers = this.getLayers(); for (var i = 0, len = layers.length; i < len; i++) { layers[i].events.on({ featureselected: this.featureSelected, featureunselected: this.featureUnselected, scope: this }); } this.bound = true; } return this.selectControl; }, /** * Unbind the selection model from the layer or SelectFeature control. * * @return {OpenLayers.Control.SelectFeature} The select feature control * this selection model used. */ unbindLayer: function() { var selectControl = this.selectControl; if (this.bound) { var layers = this.getLayers(); for (var i = 0, len = layers.length; i < len; i++) { layers[i].events.un({ featureselected: this.featureSelected, featureunselected: this.featureUnselected, scope: this }); } if (this.autoActivateControl) { selectControl.deactivate(); } this.selectControl = null; this.bound = false; } return selectControl; }, /** * Handler for when a feature is selected. * * @param {Object} evt An object with a `feature` property referencing the * selected feature. * @private */ featureSelected: function(evt) { if (!this._selecting) { var store = this.view.store; var featureKey = GeoExt.isExt4 ? 'raw' : 'data'; var row = store.findBy(function(record, id) { return record[featureKey] == evt.feature; }); if (row != -1 && !this.isSelected(row)) { this._selecting = true; this.select(row, !this.singleSelect); this._selecting = false; // focus the row in the grid to ensure it is visible this.view.focusRow(row); } } }, /** * Handler for when a feature is unselected. * * @param {Object} evt An object with a `feature` property referencing the * unselected feature. * @private */ featureUnselected: function(evt) { if (!this._selecting) { var store = this.view.store; var featureKey = GeoExt.isExt4 ? 'raw' : 'data'; var row = store.findBy(function(record, id) { return record[featureKey] == evt.feature; }); if (row != -1 && this.isSelected(row)) { this._selecting = true; this.deselect(row); this._selecting = false; this.view.focusRow(row); } } }, /** * Synchronizes selection on the layer with selection in the grid. * * @param {Ext.data.Record} record The record. * @param {Boolean} isSelected. * @private */ onSelectChange: function(record, isSelected) { this.callParent(arguments); var featureKey = GeoExt.isExt4 ? 'raw' : 'data'; var feature = record[featureKey]; if (this.selectControl && !this._selecting && feature) { var layers = this.getLayers(); if (isSelected) { for (var i = 0, len = layers.length; i < len; i++) { if (Ext.Array.indexOf(layers[i].selectedFeatures, feature) == -1) { this._selecting = true; this.selectControl.select(feature); this._selecting = false; this.selectedFeatures.push(feature); break; } } if (this.autoPanMapOnSelection) { this.recenterToSelectionExtent(); } } else { for (var i = 0, len = layers.length; i < len; i++) { if (Ext.Array.indexOf(layers[i].selectedFeatures, feature) != -1) { this._selecting = true; this.selectControl.unselect(feature); this._selecting = false; OpenLayers.Util.removeItem(this.selectedFeatures, feature); break; } } if (this.autoPanMapOnSelection && this.selectedFeatures.length > 0) { this.recenterToSelectionExtent(); } } } }, /** * Gets the layers attached to the select feature control. * * @return the layers attached to the select feature control. * @private */ getLayers: function() { return this.selectControl.layers || [this.selectControl.layer]; }, /** * Centers the map in order to display all selected features. * * @private */ recenterToSelectionExtent: function() { var map = this.selectControl.map; var selectionExtent = this.getSelectionExtent(); var selectionExtentZoom = map.getZoomForExtent(selectionExtent, false); if (selectionExtentZoom > map.getZoom()) { map.setCenter(selectionExtent.getCenterLonLat()); } else { map.zoomToExtent(selectionExtent); } }, /** * Calculates the max extent which includes all selected features. * * @return {OpenLayers.Bounds} Returns null if the layer has no features * with geometries. */ getSelectionExtent: function () { var maxExtent = null; var features = this.selectedFeatures; if (features && (features.length > 0)) { var geometry = null; for (var i = 0, len = features.length; i < len; i++) { geometry = features[i].geometry; if (geometry) { if (maxExtent === null) { maxExtent = new OpenLayers.Bounds(); } maxExtent.extend(geometry.getBounds()); } } } return maxExtent; } });