From 3838b684276458dd0a6852d634a138c925c14800 Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Mon, 23 Sep 2019 14:54:46 +0200 Subject: [PATCH] Removed SelectInteraction --- changelog/upgrade-notes.md | 4 + examples/box-selection.html | 3 +- examples/box-selection.js | 40 +- examples/earthquake-clusters.html | 2 +- examples/earthquake-clusters.js | 92 ++--- examples/icon-negative.js | 47 ++- examples/modify-features.html | 2 +- examples/modify-features.js | 42 ++- examples/modify-test.js | 28 +- examples/select-features.js | 85 ----- examples/snap.js | 50 ++- examples/translate-features.js | 40 +- examples/vector-esri-edit.js | 46 ++- src/ol/interaction.js | 1 - src/ol/interaction/Select.js | 483 ------------------------ test/spec/ol/interaction/select.test.js | 451 ---------------------- 16 files changed, 289 insertions(+), 1127 deletions(-) delete mode 100644 examples/select-features.js delete mode 100644 src/ol/interaction/Select.js delete mode 100644 test/spec/ol/interaction/select.test.js diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 478c58eff3..da6f183f2f 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -258,6 +258,10 @@ The `ol/source/Vector#refresh()` method now removes all features from the source The `getGetFeatureInfoUrl` of `ol/source/ImageWMS` and `ol/source/TileWMS` is now called `getFeatureInfoUrl`. +##### Removal of `SelectInteraction` + +The `SelectInteraction` is removed. There are two examples ([Select Features by Hover](https://openlayers.org/en/master/examples/select-hover-features.html) and [Select multiple Features](https://openlayers.org/en/master/examples/select-multiple-features.html) which show how similar results can be achieved by using more basic methods. + #### Other changes ##### Allow declutter in image render mode diff --git a/examples/box-selection.html b/examples/box-selection.html index 2660efa27c..7eeecfdfdc 100644 --- a/examples/box-selection.html +++ b/examples/box-selection.html @@ -3,8 +3,7 @@ layout: example.html title: Box Selection shortdesc: Using a DragBox interaction to select features. docs: > -

This example shows how to use a DragBox interaction to select features. Selected features are added - to the feature overlay of a select interaction (ol/interaction/Select) for highlighting.

+

This example shows how to use a DragBox interaction to select features.

Use Ctrl+Drag (Command+Drag on Mac) to draw boxes.

tags: "DragBox, feature, selection, box" --- diff --git a/examples/box-selection.js b/examples/box-selection.js index 5f6b3de1a8..821d1fa77b 100644 --- a/examples/box-selection.js +++ b/examples/box-selection.js @@ -2,9 +2,13 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import {platformModifierKeyOnly} from '../src/ol/events/condition.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; -import {DragBox, Select} from '../src/ol/interaction.js'; +import {DragBox} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import Collection from '../src/ol/Collection.js'; +import Style from '../src/ol/style/Style.js'; +import Fill from '../src/ol/style/Fill.js'; +import Stroke from '../src/ol/style/Stroke.js'; const vectorSource = new VectorSource({ @@ -30,11 +34,37 @@ const map = new Map({ }) }); -// a normal select interaction to handle click -const select = new Select(); -map.addInteraction(select); +const selectedFeatures = new Collection(); -const selectedFeatures = select.getFeatures(); +// style features in collection + +const highlightStyle = new Style({ + fill: new Fill({ + color: 'rgba(255,255,255,0.7)' + }), + stroke: new Stroke({ + color: '#3399CC', + width: 3 + }) +}); + +selectedFeatures.on('add', function(e) { + e.element.setStyle(highlightStyle); +}); + +selectedFeatures.on('remove', function(e) { + e.element.setStyle(undefined); +}); + + +// handle clicks + +map.on('singleclick', function(e) { + selectedFeatures.clear(); + map.forEachFeatureAtPixel(e.pixel, function(f) { + selectedFeatures.push(f); + }); +}); // a DragBox interaction used to select features by drawing boxes const dragBox = new DragBox({ diff --git a/examples/earthquake-clusters.html b/examples/earthquake-clusters.html index 773711a2b3..aef52ea204 100644 --- a/examples/earthquake-clusters.html +++ b/examples/earthquake-clusters.html @@ -5,7 +5,7 @@ shortdesc: Demonstrates the use of style geometries to render source features of docs: >

This example parses a KML file and renders the features as clusters on a vector layer. The styling in this example is quite involved. Single earthquake locations (rendered as stars) have a size relative to their magnitude. Clusters have an opacity relative to the number of features in the cluster, and a size that represents - the extent of the features that make up the cluster. When clicking or hovering on a cluster, the individual features that make up the cluster will be shown.

+ the extent of the features that make up the cluster. When hovering over a cluster, the individual features that make up the cluster will be shown.

To achieve this, we make heavy use of style functions.

tags: "KML, vector, style, geometry, cluster" --- diff --git a/examples/earthquake-clusters.js b/examples/earthquake-clusters.js index e328f75562..840a429ac1 100644 --- a/examples/earthquake-clusters.js +++ b/examples/earthquake-clusters.js @@ -2,7 +2,6 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import {createEmpty, getWidth, getHeight, extend} from '../src/ol/extent.js'; import KML from '../src/ol/format/KML.js'; -import {defaults as defaultInteractions, Select} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {Cluster, Stamen, Vector as VectorSource} from '../src/ol/source.js'; import {Circle as CircleStyle, Fill, RegularShape, Stroke, Style, Text} from '../src/ol/style.js'; @@ -69,48 +68,50 @@ const calculateClusterInfo = function(resolution) { }; let currentResolution; +let hovered = null; + function styleFunction(feature, resolution) { - if (resolution != currentResolution) { - calculateClusterInfo(resolution); - currentResolution = resolution; - } - let style; - const size = feature.get('features').length; - if (size > 1) { - style = new Style({ + if (feature !== hovered) { + if (resolution != currentResolution) { + calculateClusterInfo(resolution); + currentResolution = resolution; + } + let style; + const size = feature.get('features').length; + if (size > 1) { + style = new Style({ + image: new CircleStyle({ + radius: feature.get('radius'), + fill: new Fill({ + color: [255, 153, 0, Math.min(0.8, 0.4 + (size / maxFeatureCount))] + }) + }), + text: new Text({ + text: size.toString(), + fill: textFill, + stroke: textStroke + }) + }); + } else { + const originalFeature = feature.get('features')[0]; + style = createEarthquakeStyle(originalFeature); + } + return style; + } else { + const styles = [new Style({ image: new CircleStyle({ radius: feature.get('radius'), - fill: new Fill({ - color: [255, 153, 0, Math.min(0.8, 0.4 + (size / maxFeatureCount))] - }) - }), - text: new Text({ - text: size.toString(), - fill: textFill, - stroke: textStroke + fill: invisibleFill }) - }); - } else { - const originalFeature = feature.get('features')[0]; - style = createEarthquakeStyle(originalFeature); + })]; + const originalFeatures = feature.get('features'); + let originalFeature; + for (let i = originalFeatures.length - 1; i >= 0; --i) { + originalFeature = originalFeatures[i]; + styles.push(createEarthquakeStyle(originalFeature)); + } + return styles; } - return style; -} - -function selectStyleFunction(feature) { - const styles = [new Style({ - image: new CircleStyle({ - radius: feature.get('radius'), - fill: invisibleFill - }) - })]; - const originalFeatures = feature.get('features'); - let originalFeature; - for (let i = originalFeatures.length - 1; i >= 0; --i) { - originalFeature = originalFeatures[i]; - styles.push(createEarthquakeStyle(originalFeature)); - } - return styles; } vector = new VectorLayer({ @@ -134,16 +135,17 @@ const raster = new TileLayer({ const map = new Map({ layers: [raster, vector], - interactions: defaultInteractions().extend([new Select({ - condition: function(evt) { - return evt.type == 'pointermove' || - evt.type == 'singleclick'; - }, - style: selectStyleFunction - })]), target: 'map', view: new View({ center: [0, 0], zoom: 2 }) }); + +map.on('pointermove', function(e) { + hovered = null; + map.forEachFeatureAtPixel(e.pixel, function(f) { + hovered = f; + f.changed(); + }); +}); diff --git a/examples/icon-negative.js b/examples/icon-negative.js index dfa83db624..54da8dff40 100644 --- a/examples/icon-negative.js +++ b/examples/icon-negative.js @@ -2,7 +2,6 @@ import Feature from '../src/ol/Feature.js'; import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import Point from '../src/ol/geom/Point.js'; -import Select from '../src/ol/interaction/Select.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import Stamen from '../src/ol/source/Stamen.js'; import VectorSource from '../src/ol/source/Vector.js'; @@ -44,27 +43,35 @@ const map = new Map({ }); const selectStyle = {}; -const select = new Select({ - style: function(feature) { - const image = feature.get('style').getImage().getImage(); - if (!selectStyle[image.src]) { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - canvas.width = image.width; - canvas.height = image.height; - context.drawImage(image, 0, 0, image.width, image.height); - const imageData = context.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; - for (let i = 0, ii = data.length; i < ii; i = i + (i % 4 == 2 ? 2 : 1)) { - data[i] = 255 - data[i]; - } - context.putImageData(imageData, 0, 0); - selectStyle[image.src] = createStyle(undefined, canvas); - } - return selectStyle[image.src]; +let selected = null; + +map.on('singleclick', function(e) { + if (selected !== null) { + selected.setStyle(undefined); } + map.forEachFeatureAtPixel(e.pixel, function(f) { + f.setStyle(function(feature) { + const image = feature.get('style').getImage().getImage(); + if (!selectStyle[image.src]) { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.width = image.width; + canvas.height = image.height; + context.drawImage(image, 0, 0, image.width, image.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + for (let i = 0, ii = data.length; i < ii; i = i + (i % 4 == 2 ? 2 : 1)) { + data[i] = 255 - data[i]; + } + context.putImageData(imageData, 0, 0); + selectStyle[image.src] = createStyle(undefined, canvas); + } + return selectStyle[image.src]; + }); + selected = f; + return true; + }); }); -map.addInteraction(select); map.on('pointermove', function(evt) { map.getTargetElement().style.cursor = diff --git a/examples/modify-features.html b/examples/modify-features.html index 47ccc9a82b..7db85bdfc0 100644 --- a/examples/modify-features.html +++ b/examples/modify-features.html @@ -3,7 +3,7 @@ layout: example.html title: Modify Features shortdesc: Editing features with the modify interaction. docs: > -

This example demonstrates how the modify and select interactions can be used together. Zoom in to an area of interest and select a feature for editing. +

This example demonstrates how the modify interaction can be used. Zoom in to an area of interest and select a feature for editing. Then drag points around to modify the feature. You can preserve topology by selecting multiple features before editing (Shift+Click to select multiple features).

tags: "modify, edit, vector" --- diff --git a/examples/modify-features.js b/examples/modify-features.js index 3be37f11dc..cb67fb19eb 100644 --- a/examples/modify-features.js +++ b/examples/modify-features.js @@ -1,9 +1,14 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; -import {defaults as defaultInteractions, Modify, Select} from '../src/ol/interaction.js'; +import {defaults as defaultInteractions, Modify} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import {shiftKeyOnly} from '../src/ol/events/condition.js'; +import Collection from '../src/ol/Collection.js'; +import Style from '../src/ol/style/Style.js'; +import Fill from '../src/ol/style/Fill.js'; +import Stroke from '../src/ol/style/Stroke.js'; const raster = new TileLayer({ @@ -18,16 +23,34 @@ const vector = new VectorLayer({ }) }); -const select = new Select({ - wrapX: false +const features = new Collection(); + +// style features in collection + +const highlightStyle = new Style({ + fill: new Fill({ + color: 'rgba(255,255,255,0.7)' + }), + stroke: new Stroke({ + color: 'rgb(51,153,204)', + width: 3 + }) +}); + +features.on('add', function(e) { + e.element.setStyle(highlightStyle); +}); + +features.on('remove', function(e) { + e.element.setStyle(undefined); }); const modify = new Modify({ - features: select.getFeatures() + features: features }); const map = new Map({ - interactions: defaultInteractions().extend([select, modify]), + interactions: defaultInteractions().extend([modify]), layers: [raster, vector], target: 'map', view: new View({ @@ -35,3 +58,12 @@ const map = new Map({ zoom: 2 }) }); + +map.on('singleclick', function(e) { + if (!shiftKeyOnly(e)) { + features.clear(); + } + map.forEachFeatureAtPixel(e.pixel, function(f) { + features.push(f); + }); +}); diff --git a/examples/modify-test.js b/examples/modify-test.js index f399546a03..f982afb667 100644 --- a/examples/modify-test.js +++ b/examples/modify-test.js @@ -1,10 +1,11 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; -import {defaults as defaultInteractions, Modify, Select} from '../src/ol/interaction.js'; +import {defaults as defaultInteractions, Modify} from '../src/ol/interaction.js'; import VectorLayer from '../src/ol/layer/Vector.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; +import Collection from '../src/ol/Collection.js'; const styleFunction = (function() { @@ -211,23 +212,29 @@ const overlayStyle = (function() { }; })(); -const select = new Select({ - style: overlayStyle +const collection = new Collection(); + +collection.on('add', function(e) { + e.element.setStyle(overlayStyle); +}); + +collection.on('remove', function(e) { + e.element.setStyle(undefined); }); const modify = new Modify({ - features: select.getFeatures(), + features: collection, style: overlayStyle, insertVertexCondition: function() { // prevent new vertices to be added to the polygons - return !select.getFeatures().getArray().every(function(feature) { + return !collection.getArray().every(function(feature) { return feature.getGeometry().getType().match(/Polygon/); }); } }); const map = new Map({ - interactions: defaultInteractions().extend([select, modify]), + interactions: defaultInteractions().extend([modify]), layers: [layer], target: 'map', view: new View({ @@ -235,3 +242,12 @@ const map = new Map({ zoom: 2 }) }); + +map.on('singleclick', function(e) { + collection.clear(); + map.forEachFeatureAtPixel(e.pixel, function(f) { + collection.push(f); + }); +}); + + diff --git a/examples/select-features.js b/examples/select-features.js deleted file mode 100644 index 0303c58896..0000000000 --- a/examples/select-features.js +++ /dev/null @@ -1,85 +0,0 @@ -import Map from '../src/ol/Map.js'; -import View from '../src/ol/View.js'; -import {click, pointerMove, altKeyOnly} from '../src/ol/events/condition.js'; -import GeoJSON from '../src/ol/format/GeoJSON.js'; -import Select from '../src/ol/interaction/Select.js'; -import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; -import OSM from '../src/ol/source/OSM.js'; -import VectorSource from '../src/ol/source/Vector.js'; - -const raster = new TileLayer({ - source: new OSM() -}); - -const vector = new VectorLayer({ - source: new VectorSource({ - url: 'data/geojson/countries.geojson', - format: new GeoJSON() - }) -}); - -const map = new Map({ - layers: [raster, vector], - target: 'map', - view: new View({ - center: [0, 0], - zoom: 2 - }) -}); - -let select = null; // ref to currently selected interaction - -// select interaction working on "singleclick" -const selectSingleClick = new Select(); - -// select interaction working on "click" -const selectClick = new Select({ - condition: click -}); - -// select interaction working on "pointermove" -const selectPointerMove = new Select({ - condition: pointerMove -}); - -const selectAltClick = new Select({ - condition: function(mapBrowserEvent) { - return click(mapBrowserEvent) && altKeyOnly(mapBrowserEvent); - } -}); - -const selectElement = document.getElementById('type'); - -const changeInteraction = function() { - if (select !== null) { - map.removeInteraction(select); - } - const value = selectElement.value; - if (value == 'singleclick') { - select = selectSingleClick; - } else if (value == 'click') { - select = selectClick; - } else if (value == 'pointermove') { - select = selectPointerMove; - } else if (value == 'altclick') { - select = selectAltClick; - } else { - select = null; - } - if (select !== null) { - map.addInteraction(select); - select.on('select', function(e) { - document.getElementById('status').innerHTML = ' ' + - e.target.getFeatures().getLength() + - ' selected features (last operation selected ' + e.selected.length + - ' and deselected ' + e.deselected.length + ' features)'; - }); - } -}; - - -/** - * onchange callback on the select element. - */ -selectElement.onchange = changeInteraction; -changeInteraction(); diff --git a/examples/snap.js b/examples/snap.js index ab5d16d24c..fbd4ee4b42 100644 --- a/examples/snap.js +++ b/examples/snap.js @@ -1,9 +1,11 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; -import {Draw, Modify, Select, Snap} from '../src/ol/interaction.js'; +import {Draw, Modify, Snap} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; +import Collection from '../src/ol/Collection.js'; +import {shiftKeyOnly} from '../src/ol/events/condition.js'; const raster = new TileLayer({ source: new OSM() @@ -37,29 +39,53 @@ const map = new Map({ }) }); +const highlightStyle = new Style({ + fill: new Fill({ + color: 'rgba(255,255,255,0.7)' + }), + stroke: new Stroke({ + color: 'rgb(51,153,204)', + width: 3 + }) +}); + const ExampleModify = { init: function() { - this.select = new Select(); - map.addInteraction(this.select); + this.features = new Collection(); + + this.features.on('add', function(e) { + e.element.setStyle(highlightStyle); + }); + + this.features.on('remove', function(e) { + e.element.setStyle(undefined); + }); + + this.select = function(e) { + if (!shiftKeyOnly(e)) { + this.features.clear(); + } + map.forEachFeatureAtPixel(e.pixel, function(f) { + this.features.push(f); + }.bind(this)); + }.bind(this); this.modify = new Modify({ - features: this.select.getFeatures() + features: this.features }); map.addInteraction(this.modify); this.setEvents(); }, setEvents: function() { - const selectedFeatures = this.select.getFeatures(); - - this.select.on('change:active', function() { - selectedFeatures.forEach(function(each) { - selectedFeatures.remove(each); - }); - }); }, setActive: function(active) { - this.select.setActive(active); + if (active) { + this.features.clear(); + map.on('singleclick', this.select); + } else { + map.un('singleclick', this.select); + } this.modify.setActive(active); } }; diff --git a/examples/translate-features.js b/examples/translate-features.js index fa2aa4d57a..f9919aae2d 100644 --- a/examples/translate-features.js +++ b/examples/translate-features.js @@ -1,10 +1,15 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; -import {defaults as defaultInteractions, Select, Translate} from '../src/ol/interaction.js'; +import {defaults as defaultInteractions, Translate} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import OSM from '../src/ol/source/OSM.js'; import VectorSource from '../src/ol/source/Vector.js'; +import Collection from '../src/ol/Collection.js'; +import Style from '../src/ol/style/Style.js'; +import Fill from '../src/ol/style/Fill.js'; +import Stroke from '../src/ol/style/Stroke.js'; +import {shiftKeyOnly} from '../src/ol/events/condition.js'; const raster = new TileLayer({ @@ -18,14 +23,32 @@ const vector = new VectorLayer({ }) }); -const select = new Select(); +const features = new Collection(); + +const highlightStyle = new Style({ + fill: new Fill({ + color: 'rgba(255,255,255,0.7)' + }), + stroke: new Stroke({ + color: 'rgb(51,153,204)', + width: 3 + }) +}); + +features.on('add', function(e) { + e.element.setStyle(highlightStyle); +}); + +features.on('remove', function(e) { + e.element.setStyle(undefined); +}); const translate = new Translate({ - features: select.getFeatures() + features: features }); const map = new Map({ - interactions: defaultInteractions().extend([select, translate]), + interactions: defaultInteractions().extend([translate]), layers: [raster, vector], target: 'map', view: new View({ @@ -33,3 +56,12 @@ const map = new Map({ zoom: 2 }) }); + +map.on('singleclick', function(e) { + if (!shiftKeyOnly(e)) { + features.clear(); + } + map.forEachFeatureAtPixel(e.pixel, function(f) { + features.push(f); + }); +}); diff --git a/examples/vector-esri-edit.js b/examples/vector-esri-edit.js index 6fa31162f4..768aea110b 100644 --- a/examples/vector-esri-edit.js +++ b/examples/vector-esri-edit.js @@ -1,13 +1,18 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import EsriJSON from '../src/ol/format/EsriJSON.js'; -import {defaults as defaultInteractions, Draw, Modify, Select} from '../src/ol/interaction.js'; +import {defaults as defaultInteractions, Draw, Modify} from '../src/ol/interaction.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {tile as tileStrategy} from '../src/ol/loadingstrategy.js'; import {fromLonLat} from '../src/ol/proj.js'; import VectorSource from '../src/ol/source/Vector.js'; import XYZ from '../src/ol/source/XYZ.js'; import {createXYZ} from '../src/ol/tilegrid.js'; +import Collection from '../src/ol/Collection.js'; +import Style from '../src/ol/style/Style.js'; +import Fill from '../src/ol/style/Fill.js'; +import Stroke from '../src/ol/style/Stroke.js'; +import {shiftKeyOnly} from '../src/ol/events/condition.js'; const serviceUrl = 'https://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/' + @@ -63,17 +68,32 @@ const draw = new Draw({ type: 'Polygon' }); -const select = new Select(); -select.setActive(false); -const selected = select.getFeatures(); +const selected = new Collection(); + +const highlightStyle = new Style({ + fill: new Fill({ + color: 'rgba(255,255,255,0.7)' + }), + stroke: new Stroke({ + color: 'rgb(51,153,204)', + width: 3 + }) +}); + +selected.on('add', function(e) { + e.element.setStyle(highlightStyle); +}); + +selected.on('remove', function(e) { + e.element.setStyle(undefined); +}); const modify = new Modify({ features: selected }); -modify.setActive(false); const map = new Map({ - interactions: defaultInteractions().extend([draw, select, modify]), + interactions: defaultInteractions().extend([draw, modify]), layers: [raster, vector], target: document.getElementById('map'), view: new View({ @@ -82,6 +102,15 @@ const map = new Map({ }) }); +function select(e) { + if (!shiftKeyOnly(e)) { + selected.clear(); + } + map.forEachFeatureAtPixel(e.pixel, function(f) { + selected.push(f); + }); +} + const typeSelect = document.getElementById('type'); @@ -92,6 +121,11 @@ typeSelect.onchange = function() { draw.setActive(typeSelect.value === 'DRAW'); select.setActive(typeSelect.value === 'MODIFY'); modify.setActive(typeSelect.value === 'MODIFY'); + if (typeSelect.value === 'MODIFY') { + map.on('singleclick', select); + } else { + map.un('singleclick', select); + } }; const dirty = {}; diff --git a/src/ol/interaction.js b/src/ol/interaction.js index ce91f62bd5..3f5baf7c41 100644 --- a/src/ol/interaction.js +++ b/src/ol/interaction.js @@ -31,7 +31,6 @@ export {default as MouseWheelZoom} from './interaction/MouseWheelZoom.js'; export {default as PinchRotate} from './interaction/PinchRotate.js'; export {default as PinchZoom} from './interaction/PinchZoom.js'; export {default as Pointer} from './interaction/Pointer.js'; -export {default as Select} from './interaction/Select.js'; export {default as Snap} from './interaction/Snap.js'; export {default as Translate} from './interaction/Translate.js'; diff --git a/src/ol/interaction/Select.js b/src/ol/interaction/Select.js deleted file mode 100644 index 497d6f0212..0000000000 --- a/src/ol/interaction/Select.js +++ /dev/null @@ -1,483 +0,0 @@ -/** - * @module ol/interaction/Select - */ -import {getUid} from '../util.js'; -import CollectionEventType from '../CollectionEventType.js'; -import {extend, includes} from '../array.js'; -import Event from '../events/Event.js'; -import {singleClick, never, shiftKeyOnly, pointerMove} from '../events/condition.js'; -import {TRUE} from '../functions.js'; -import GeometryType from '../geom/GeometryType.js'; -import Interaction from './Interaction.js'; -import VectorLayer from '../layer/Vector.js'; -import {clear} from '../obj.js'; -import VectorSource from '../source/Vector.js'; -import {createEditingStyle} from '../style/Style.js'; - - -/** - * @enum {string} - */ -const SelectEventType = { - /** - * Triggered when feature(s) has been (de)selected. - * @event SelectEvent#select - * @api - */ - SELECT: 'select' -}; - - -/** - * A function that takes an {@link module:ol/Feature} or - * {@link module:ol/render/Feature} and an - * {@link module:ol/layer/Layer} and returns `true` if the feature may be - * selected or `false` otherwise. - * @typedef {function(import("../Feature.js").FeatureLike, import("../layer/Layer.js").default):boolean} FilterFunction - */ - - -/** - * @typedef {Object} Options - * @property {import("../events/condition.js").Condition} [addCondition] A function - * that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a - * boolean to indicate whether that event should be handled. - * By default, this is {@link module:ol/events/condition~never}. Use this if you - * want to use different events for add and remove instead of `toggle`. - * @property {import("../events/condition.js").Condition} [condition] A function that - * takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a - * boolean to indicate whether that event should be handled. This is the event - * for the selected features as a whole. By default, this is - * {@link module:ol/events/condition~singleClick}. Clicking on a feature selects that - * feature and removes any that were in the selection. Clicking outside any - * feature removes all from the selection. - * See `toggle`, `add`, `remove` options for adding/removing extra features to/ - * from the selection. - * @property {Array|function(import("../layer/Layer.js").default): boolean} [layers] - * A list of layers from which features should be selected. Alternatively, a - * filter function can be provided. The function will be called for each layer - * in the map and should return `true` for layers that you want to be - * selectable. If the option is absent, all visible layers will be considered - * selectable. - * @property {import("../style/Style.js").StyleLike} [style] - * Style for the selected features. By default the default edit style is used - * (see {@link module:ol/style}). - * @property {import("../events/condition.js").Condition} [removeCondition] A function - * that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a - * boolean to indicate whether that event should be handled. - * By default, this is {@link module:ol/events/condition~never}. Use this if you - * want to use different events for add and remove instead of `toggle`. - * @property {import("../events/condition.js").Condition} [toggleCondition] A function - * that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a - * boolean to indicate whether that event should be handled. This is in addition - * to the `condition` event. By default, - * {@link module:ol/events/condition~shiftKeyOnly}, i.e. pressing `shift` as - * well as the `condition` event, adds that feature to the current selection if - * it is not currently selected, and removes it if it is. See `add` and `remove` - * if you want to use different events instead of a toggle. - * @property {boolean} [multi=false] A boolean that determines if the default - * behaviour should select only single features or all (overlapping) features at - * the clicked map position. The default of `false` means single select. - * @property {import("../Collection.js").default} [features] - * Collection where the interaction will place selected features. Optional. If - * not set the interaction will create a collection. In any case the collection - * used by the interaction is returned by - * {@link module:ol/interaction/Select~Select#getFeatures}. - * @property {FilterFunction} [filter] A function - * that takes an {@link module:ol/Feature} and an - * {@link module:ol/layer/Layer} and returns `true` if the feature may be - * selected or `false` otherwise. - * @property {boolean} [wrapX=true] Wrap the world horizontally on the selection - * overlay. - * @property {number} [hitTolerance=0] Hit-detection tolerance. Pixels inside - * the radius around the given position will be checked for features. - */ - - -/** - * @classdesc - * Events emitted by {@link module:ol/interaction/Select~Select} instances are instances of - * this type. - */ -class SelectEvent extends Event { - /** - * @param {SelectEventType} type The event type. - * @param {Array} selected Selected features. - * @param {Array} deselected Deselected features. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Associated - * {@link module:ol/MapBrowserEvent}. - */ - constructor(type, selected, deselected, mapBrowserEvent) { - super(type); - - /** - * Selected features array. - * @type {Array} - * @api - */ - this.selected = selected; - - /** - * Deselected features array. - * @type {Array} - * @api - */ - this.deselected = deselected; - - /** - * Associated {@link module:ol/MapBrowserEvent}. - * @type {import("../MapBrowserEvent.js").default} - * @api - */ - this.mapBrowserEvent = mapBrowserEvent; - - } - -} - - -/** - * @classdesc - * Interaction for selecting vector features. By default, selected features are - * styled differently, so this interaction can be used for visual highlighting, - * as well as selecting features for other actions, such as modification or - * output. There are three ways of controlling which features are selected: - * using the browser event as defined by the `condition` and optionally the - * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a - * further feature filter using the `filter` option. - * - * Selected features are added to an internal unmanaged layer. - * - * @fires SelectEvent - * @api - */ -class Select extends Interaction { - /** - * @param {Options=} opt_options Options. - */ - constructor(opt_options) { - - super({ - handleEvent: handleEvent - }); - - const options = opt_options ? opt_options : {}; - - /** - * @private - * @type {import("../events/condition.js").Condition} - */ - this.condition_ = options.condition ? options.condition : singleClick; - - /** - * @private - * @type {import("../events/condition.js").Condition} - */ - this.addCondition_ = options.addCondition ? options.addCondition : never; - - /** - * @private - * @type {import("../events/condition.js").Condition} - */ - this.removeCondition_ = options.removeCondition ? options.removeCondition : never; - - /** - * @private - * @type {import("../events/condition.js").Condition} - */ - this.toggleCondition_ = options.toggleCondition ? options.toggleCondition : shiftKeyOnly; - - /** - * @private - * @type {boolean} - */ - this.multi_ = options.multi ? options.multi : false; - - /** - * @private - * @type {FilterFunction} - */ - this.filter_ = options.filter ? options.filter : TRUE; - - /** - * @private - * @type {number} - */ - this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0; - - const featureOverlay = new VectorLayer({ - source: new VectorSource({ - useSpatialIndex: false, - features: options.features, - wrapX: options.wrapX - }), - style: options.style ? options.style : - getDefaultStyleFunction(), - updateWhileAnimating: true, - updateWhileInteracting: true - }); - - /** - * @private - * @type {VectorLayer} - */ - this.featureOverlay_ = featureOverlay; - - /** @type {function(import("../layer/Layer.js").default): boolean} */ - let layerFilter; - if (options.layers) { - if (typeof options.layers === 'function') { - layerFilter = options.layers; - } else { - const layers = options.layers; - layerFilter = function(layer) { - return includes(layers, layer); - }; - } - } else { - layerFilter = TRUE; - } - - /** - * @private - * @type {function(import("../layer/Layer.js").default): boolean} - */ - this.layerFilter_ = layerFilter; - - /** - * An association between selected feature (key) - * and layer (value) - * @private - * @type {Object} - */ - this.featureLayerAssociation_ = {}; - - const features = this.getFeatures(); - features.addEventListener(CollectionEventType.ADD, this.addFeature_.bind(this)); - features.addEventListener(CollectionEventType.REMOVE, this.removeFeature_.bind(this)); - } - - /** - * @param {import("../Feature.js").FeatureLike} feature Feature. - * @param {import("../layer/Layer.js").default} layer Layer. - * @private - */ - addFeatureLayerAssociation_(feature, layer) { - this.featureLayerAssociation_[getUid(feature)] = layer; - } - - /** - * Get the selected features. - * @return {import("../Collection.js").default} Features collection. - * @api - */ - getFeatures() { - return this.featureOverlay_.getSource().getFeaturesCollection(); - } - - /** - * Returns the Hit-detection tolerance. - * @returns {number} Hit tolerance in pixels. - * @api - */ - getHitTolerance() { - return this.hitTolerance_; - } - - /** - * Returns the associated {@link module:ol/layer/Vector~Vector vectorlayer} of - * the (last) selected feature. Note that this will not work with any - * programmatic method like pushing features to - * {@link module:ol/interaction/Select~Select#getFeatures collection}. - * @param {import("../Feature.js").FeatureLike} feature Feature - * @return {VectorLayer} Layer. - * @api - */ - getLayer(feature) { - return ( - /** @type {VectorLayer} */ (this.featureLayerAssociation_[getUid(feature)]) - ); - } - - /** - * Get the overlay layer that this interaction renders selected features to. - * @return {VectorLayer} Overlay layer. - * @api - */ - getOverlay() { - return this.featureOverlay_; - } - - /** - * Hit-detection tolerance. Pixels inside the radius around the given position - * will be checked for features. - * @param {number} hitTolerance Hit tolerance in pixels. - * @api - */ - setHitTolerance(hitTolerance) { - this.hitTolerance_ = hitTolerance; - } - - /** - * Remove the interaction from its current map, if any, and attach it to a new - * map, if any. Pass `null` to just remove the interaction from the current map. - * @param {import("../PluggableMap.js").default} map Map. - * @override - * @api - */ - setMap(map) { - const currentMap = this.getMap(); - const selectedFeatures = this.getFeatures(); - if (currentMap) { - selectedFeatures.forEach(currentMap.unskipFeature.bind(currentMap)); - } - super.setMap(map); - this.featureOverlay_.setMap(map); - if (map) { - selectedFeatures.forEach(map.skipFeature.bind(map)); - } - } - - /** - * @param {import("../Collection.js").CollectionEvent} evt Event. - * @private - */ - addFeature_(evt) { - const map = this.getMap(); - if (map) { - map.skipFeature(/** @type {import("../Feature.js").default} */ (evt.element)); - } - } - - /** - * @param {import("../Collection.js").CollectionEvent} evt Event. - * @private - */ - removeFeature_(evt) { - const map = this.getMap(); - if (map) { - map.unskipFeature(/** @type {import("../Feature.js").default} */ (evt.element)); - } - } - - /** - * @param {import("../Feature.js").FeatureLike} feature Feature. - * @private - */ - removeFeatureLayerAssociation_(feature) { - delete this.featureLayerAssociation_[getUid(feature)]; - } -} - - -/** - * Handles the {@link module:ol/MapBrowserEvent map browser event} and may change the - * selected state of features. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {Select} - */ -function handleEvent(mapBrowserEvent) { - if (!this.condition_(mapBrowserEvent)) { - return true; - } - const add = this.addCondition_(mapBrowserEvent); - const remove = this.removeCondition_(mapBrowserEvent); - const toggle = this.toggleCondition_(mapBrowserEvent); - const set = !add && !remove && !toggle; - const map = mapBrowserEvent.map; - const features = this.getFeatures(); - const deselected = []; - const selected = []; - if (set) { - // Replace the currently selected feature(s) with the feature(s) at the - // pixel, or clear the selected feature(s) if there is no feature at - // the pixel. - clear(this.featureLayerAssociation_); - map.forEachFeatureAtPixel(mapBrowserEvent.pixel, - ( - /** - * @param {import("../Feature.js").FeatureLike} feature Feature. - * @param {import("../layer/Layer.js").default} layer Layer. - * @return {boolean|undefined} Continue to iterate over the features. - */ - function(feature, layer) { - if (this.filter_(feature, layer)) { - selected.push(feature); - this.addFeatureLayerAssociation_(feature, layer); - return !this.multi_; - } - }).bind(this), { - layerFilter: this.layerFilter_, - hitTolerance: this.hitTolerance_ - }); - for (let i = features.getLength() - 1; i >= 0; --i) { - const feature = features.item(i); - const index = selected.indexOf(feature); - if (index > -1) { - // feature is already selected - selected.splice(index, 1); - } else { - features.remove(feature); - deselected.push(feature); - } - } - if (selected.length !== 0) { - features.extend(selected); - } - } else { - // Modify the currently selected feature(s). - map.forEachFeatureAtPixel(mapBrowserEvent.pixel, - ( - /** - * @param {import("../Feature.js").FeatureLike} feature Feature. - * @param {import("../layer/Layer.js").default} layer Layer. - * @return {boolean|undefined} Continue to iterate over the features. - */ - function(feature, layer) { - if (this.filter_(feature, layer)) { - if ((add || toggle) && !includes(features.getArray(), feature)) { - selected.push(feature); - this.addFeatureLayerAssociation_(feature, layer); - } else if ((remove || toggle) && includes(features.getArray(), feature)) { - deselected.push(feature); - this.removeFeatureLayerAssociation_(feature); - } - return !this.multi_; - } - }).bind(this), { - layerFilter: this.layerFilter_, - hitTolerance: this.hitTolerance_ - }); - for (let j = deselected.length - 1; j >= 0; --j) { - features.remove(deselected[j]); - } - features.extend(selected); - } - if (selected.length > 0 || deselected.length > 0) { - this.dispatchEvent( - new SelectEvent(SelectEventType.SELECT, - selected, deselected, mapBrowserEvent)); - } - return pointerMove(mapBrowserEvent); -} - - -/** - * @return {import("../style/Style.js").StyleFunction} Styles. - */ -function getDefaultStyleFunction() { - const styles = createEditingStyle(); - extend(styles[GeometryType.POLYGON], styles[GeometryType.LINE_STRING]); - extend(styles[GeometryType.GEOMETRY_COLLECTION], styles[GeometryType.LINE_STRING]); - - return function(feature, resolution) { - if (!feature.getGeometry()) { - return null; - } - return styles[feature.getGeometry().getType()]; - }; -} - - -export default Select; diff --git a/test/spec/ol/interaction/select.test.js b/test/spec/ol/interaction/select.test.js deleted file mode 100644 index 9dc9e562e1..0000000000 --- a/test/spec/ol/interaction/select.test.js +++ /dev/null @@ -1,451 +0,0 @@ -import Collection from '../../../../src/ol/Collection.js'; -import Feature from '../../../../src/ol/Feature.js'; -import Map from '../../../../src/ol/Map.js'; -import MapBrowserEventType from '../../../../src/ol/MapBrowserEventType.js'; -import MapBrowserPointerEvent from '../../../../src/ol/MapBrowserPointerEvent.js'; -import View from '../../../../src/ol/View.js'; -import Polygon from '../../../../src/ol/geom/Polygon.js'; -import Interaction from '../../../../src/ol/interaction/Interaction.js'; -import Select from '../../../../src/ol/interaction/Select.js'; -import VectorLayer from '../../../../src/ol/layer/Vector.js'; -import VectorSource from '../../../../src/ol/source/Vector.js'; - - -describe('ol.interaction.Select', function() { - let target, map, layer, source; - - const width = 360; - const height = 180; - - beforeEach(function(done) { - target = document.createElement('div'); - - const style = target.style; - style.position = 'absolute'; - style.left = '-1000px'; - style.top = '-1000px'; - style.width = width + 'px'; - style.height = height + 'px'; - document.body.appendChild(target); - - const geometry = new Polygon([[[0, 0], [0, 40], [40, 40], [40, 0]]]); - - // Four overlapping features, two features of type "foo" and two features - // of type "bar". The rendering order is, from top to bottom, foo -> bar - // -> foo -> bar. - const features = []; - features.push( - new Feature({ - geometry: geometry, - type: 'bar' - }), - new Feature({ - geometry: geometry, - type: 'foo' - }), - new Feature({ - geometry: geometry, - type: 'bar' - }), - new Feature({ - geometry: geometry, - type: 'foo' - })); - - source = new VectorSource({ - features: features - }); - - layer = new VectorLayer({source: source}); - - map = new Map({ - target: target, - layers: [layer], - view: new View({ - projection: 'EPSG:4326', - center: [0, 0], - resolution: 1 - }) - }); - - map.once('postrender', function() { - done(); - }); - }); - - afterEach(function() { - map.dispose(); - document.body.removeChild(target); - }); - - /** - * Simulates a browser event on the map viewport. The client x/y location - * will be adjusted as if the map were centered at 0,0. - * @param {string} type Event type. - * @param {number} x Horizontal offset from map center. - * @param {number} y Vertical offset from map center. - * @param {boolean=} opt_shiftKey Shift key is pressed. - */ - function simulateEvent(type, x, y, opt_shiftKey) { - const viewport = map.getViewport(); - // calculated in case body has top < 0 (test runner with small window) - const position = viewport.getBoundingClientRect(); - const shiftKey = opt_shiftKey !== undefined ? opt_shiftKey : false; - const event = new PointerEvent(type, { - clientX: position.left + x + width / 2, - clientY: position.top + y + height / 2, - shiftKey: shiftKey - }); - map.handleMapBrowserEvent(new MapBrowserPointerEvent(type, map, event)); - } - - describe('constructor', function() { - - it('creates a new interaction', function() { - const select = new Select(); - expect(select).to.be.a(Select); - expect(select).to.be.a(Interaction); - }); - - describe('user-provided collection', function() { - - it('uses the user-provided collection', function() { - const features = new Collection(); - const select = new Select({features: features}); - expect(select.getFeatures()).to.be(features); - }); - - }); - - }); - - describe('selecting a polygon', function() { - let select; - - beforeEach(function() { - select = new Select(); - map.addInteraction(select); - }); - - it('select with single-click', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(1); - }); - select.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20); - - expect(listenerSpy.callCount).to.be(1); - - const features = select.getFeatures(); - expect(features.getLength()).to.equal(1); - }); - - it('single-click outside the geometry', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(1); - }); - select.on('select', listenerSpy); - - simulateEvent(MapBrowserEventType.SINGLECLICK, -10, -10); - - expect(listenerSpy.callCount).to.be(0); - - const features = select.getFeatures(); - expect(features.getLength()).to.equal(0); - }); - - it('select twice with single-click', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(1); - }); - select.on('select', listenerSpy); - - simulateEvent(MapBrowserEventType.SINGLECLICK, 10, -20); - simulateEvent(MapBrowserEventType.SINGLECLICK, 9, -21); - - expect(listenerSpy.callCount).to.be(1); - - const features = select.getFeatures(); - expect(features.getLength()).to.equal(1); - }); - - it('select with shift single-click', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(1); - }); - select.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20, true); - - expect(listenerSpy.callCount).to.be(1); - - const features = select.getFeatures(); - expect(features.getLength()).to.equal(1); - }); - }); - - describe('multiselecting polygons', function() { - let select; - - beforeEach(function() { - select = new Select({ - multi: true - }); - map.addInteraction(select); - }); - - it('select with single-click', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(4); - }); - select.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20); - - expect(listenerSpy.callCount).to.be(1); - - const features = select.getFeatures(); - expect(features.getLength()).to.equal(4); - }); - - it('select with shift single-click', function() { - const listenerSpy = sinon.spy(function(e) { - expect(e.selected).to.have.length(4); - }); - select.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20, true); - - expect(listenerSpy.callCount).to.be(1); - - let features = select.getFeatures(); - expect(features.getLength()).to.equal(4); - expect(select.getLayer(features.item(0))).to.equal(layer); - - // Select again to make sure the internal layer isn't reported - simulateEvent('singleclick', 10, -20); - - expect(listenerSpy.callCount).to.be(1); - - features = select.getFeatures(); - expect(features.getLength()).to.equal(4); - expect(select.getLayer(features.item(0))).to.equal(layer); - }); - }); - - describe('toggle selecting polygons', function() { - let select; - - beforeEach(function() { - select = new Select({ - multi: true - }); - map.addInteraction(select); - }); - - it('with SHIFT + single-click', function() { - const listenerSpy = sinon.spy(); - select.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20, true); - - expect(listenerSpy.callCount).to.be(1); - - let features = select.getFeatures(); - expect(features.getLength()).to.equal(4); - - map.renderSync(); - - simulateEvent('singleclick', 10, -20, true); - - expect(listenerSpy.callCount).to.be(2); - - features = select.getFeatures(); - expect(features.getLength()).to.equal(0); - }); - }); - - describe('filter features using the filter option', function() { - - describe('with multi set to true', function() { - - it('only selects features that pass the filter', function() { - const select = new Select({ - multi: true, - filter: function(feature, layer) { - return feature.get('type') === 'bar'; - } - }); - map.addInteraction(select); - - simulateEvent('singleclick', 10, -20); - const features = select.getFeatures(); - expect(features.getLength()).to.equal(2); - expect(features.item(0).get('type')).to.be('bar'); - expect(features.item(1).get('type')).to.be('bar'); - }); - - it('only selects features that pass the filter ' + - 'using shift single-click', function() { - const select = new Select({ - multi: true, - filter: function(feature, layer) { - return feature.get('type') === 'bar'; - } - }); - map.addInteraction(select); - - simulateEvent('singleclick', 10, -20, - true); - const features = select.getFeatures(); - expect(features.getLength()).to.equal(2); - expect(features.item(0).get('type')).to.be('bar'); - expect(features.item(1).get('type')).to.be('bar'); - }); - }); - - describe('with multi set to false', function() { - - it('only selects the first feature that passes the filter', function() { - const select = new Select({ - multi: false, - filter: function(feature, layer) { - return feature.get('type') === 'bar'; - } - }); - map.addInteraction(select); - simulateEvent('singleclick', 10, -20); - const features = select.getFeatures(); - expect(features.getLength()).to.equal(1); - expect(features.item(0).get('type')).to.be('bar'); - }); - - it('only selects the first feature that passes the filter ' + - 'using shift single-click', function() { - const select = new Select({ - multi: false, - filter: function(feature, layer) { - return feature.get('type') === 'bar'; - } - }); - map.addInteraction(select); - simulateEvent('singleclick', 10, -20, - true); - const features = select.getFeatures(); - expect(features.getLength()).to.equal(1); - expect(features.item(0).get('type')).to.be('bar'); - }); - }); - }); - - describe('#getLayer(feature)', function() { - let interaction; - - beforeEach(function() { - interaction = new Select(); - map.addInteraction(interaction); - }); - afterEach(function() { - map.removeInteraction(interaction); - }); - - it('returns a layer from a selected feature', function() { - const listenerSpy = sinon.spy(function(e) { - const feature = e.selected[0]; - const layer_ = interaction.getLayer(feature); - expect(e.selected).to.have.length(1); - expect(feature).to.be.a(Feature); - expect(layer_).to.be.a(VectorLayer); - expect(layer_).to.equal(layer); - }); - interaction.on('select', listenerSpy); - - simulateEvent('singleclick', 10, -20); - // Select again to make sure that the internal layer doesn't get reported. - simulateEvent('singleclick', 10, -20); - }); - }); - - describe('#setActive()', function() { - let interaction; - - beforeEach(function() { - interaction = new Select(); - - expect(interaction.getActive()).to.be(true); - - map.addInteraction(interaction); - - expect(interaction.featureOverlay_).not.to.be(null); - - simulateEvent('singleclick', 10, -20); - }); - - afterEach(function() { - map.removeInteraction(interaction); - }); - - describe('#setActive(false)', function() { - it('keeps the the selection', function() { - interaction.setActive(false); - expect(interaction.getFeatures().getLength()).to.equal(1); - }); - }); - - describe('#setActive(true)', function() { - beforeEach(function() { - interaction.setActive(false); - }); - it('fires change:active', function() { - const listenerSpy = sinon.spy(); - interaction.on('change:active', listenerSpy); - interaction.setActive(true); - expect(listenerSpy.callCount).to.be(1); - }); - }); - - }); - - describe('#setMap()', function() { - let interaction; - - beforeEach(function() { - interaction = new Select(); - expect(interaction.getActive()).to.be(true); - }); - - describe('#setMap(null)', function() { - beforeEach(function() { - map.addInteraction(interaction); - }); - afterEach(function() { - map.removeInteraction(interaction); - }); - describe('#setMap(null) when interaction is active', function() { - it('unsets the map from the feature overlay', function() { - const spy = sinon.spy(interaction.featureOverlay_, 'setMap'); - interaction.setMap(null); - expect(spy.getCall(0).args[0]).to.be(null); - }); - }); - }); - - describe('#setMap(map)', function() { - describe('#setMap(map) when interaction is active', function() { - it('sets the map into the feature overlay', function() { - const spy = sinon.spy(interaction.featureOverlay_, 'setMap'); - interaction.setMap(map); - expect(spy.getCall(0).args[0]).to.be(map); - }); - }); - }); - }); - - describe('#getOverlay', function() { - it('returns the feature overlay layer', function() { - const select = new Select(); - expect (select.getOverlay()).to.eql(select.featureOverlay_); - }); - }); -});