SelectFeature.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Control.js
  7. * @requires OpenLayers/Feature/Vector.js
  8. * @requires OpenLayers/Handler/Feature.js
  9. * @requires OpenLayers/Layer/Vector/RootContainer.js
  10. */
  11. /**
  12. * Class: OpenLayers.Control.SelectFeature
  13. * The SelectFeature control selects vector features from a given layer on
  14. * click or hover.
  15. *
  16. * Inherits from:
  17. * - <OpenLayers.Control>
  18. */
  19. OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
  20. /**
  21. * APIProperty: events
  22. * {<OpenLayers.Events>} Events instance for listeners and triggering
  23. * control specific events.
  24. *
  25. * Register a listener for a particular event with the following syntax:
  26. * (code)
  27. * control.events.register(type, obj, listener);
  28. * (end)
  29. *
  30. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  31. * beforefeaturehighlighted - Triggered before a feature is highlighted
  32. * featurehighlighted - Triggered when a feature is highlighted
  33. * featureunhighlighted - Triggered when a feature is unhighlighted
  34. * boxselectionstart - Triggered before box selection starts
  35. * boxselectionend - Triggered after box selection ends
  36. */
  37. /**
  38. * Property: multipleKey
  39. * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
  40. * the <multiple> property to true. Default is null.
  41. */
  42. multipleKey: null,
  43. /**
  44. * Property: toggleKey
  45. * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
  46. * the <toggle> property to true. Default is null.
  47. */
  48. toggleKey: null,
  49. /**
  50. * APIProperty: multiple
  51. * {Boolean} Allow selection of multiple geometries. Default is false.
  52. */
  53. multiple: false,
  54. /**
  55. * APIProperty: clickout
  56. * {Boolean} Unselect features when clicking outside any feature.
  57. * Default is true.
  58. */
  59. clickout: true,
  60. /**
  61. * APIProperty: toggle
  62. * {Boolean} Unselect a selected feature on click. Default is false. Only
  63. * has meaning if hover is false.
  64. */
  65. toggle: false,
  66. /**
  67. * APIProperty: hover
  68. * {Boolean} Select on mouse over and deselect on mouse out. If true, this
  69. * ignores clicks and only listens to mouse moves.
  70. */
  71. hover: false,
  72. /**
  73. * APIProperty: highlightOnly
  74. * {Boolean} If true do not actually select features (that is place them in
  75. * the layer's selected features array), just highlight them. This property
  76. * has no effect if hover is false. Defaults to false.
  77. */
  78. highlightOnly: false,
  79. /**
  80. * APIProperty: box
  81. * {Boolean} Allow feature selection by drawing a box.
  82. */
  83. box: false,
  84. /**
  85. * Property: onBeforeSelect
  86. * {Function} Optional function to be called before a feature is selected.
  87. * The function should expect to be called with a feature.
  88. */
  89. onBeforeSelect: function() {},
  90. /**
  91. * APIProperty: onSelect
  92. * {Function} Optional function to be called when a feature is selected.
  93. * The function should expect to be called with a feature.
  94. */
  95. onSelect: function() {},
  96. /**
  97. * APIProperty: onUnselect
  98. * {Function} Optional function to be called when a feature is unselected.
  99. * The function should expect to be called with a feature.
  100. */
  101. onUnselect: function() {},
  102. /**
  103. * Property: scope
  104. * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
  105. * callbacks. If null the scope will be this control.
  106. */
  107. scope: null,
  108. /**
  109. * APIProperty: geometryTypes
  110. * {Array(String)} To restrict selecting to a limited set of geometry types,
  111. * send a list of strings corresponding to the geometry class names.
  112. */
  113. geometryTypes: null,
  114. /**
  115. * Property: layer
  116. * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
  117. * root for all layers this control is configured with (if an array of
  118. * layers was passed to the constructor), or the vector layer the control
  119. * was configured with (if a single layer was passed to the constructor).
  120. */
  121. layer: null,
  122. /**
  123. * Property: layers
  124. * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
  125. * or null if the control was configured with a single layer
  126. */
  127. layers: null,
  128. /**
  129. * APIProperty: callbacks
  130. * {Object} The functions that are sent to the handlers.feature for callback
  131. */
  132. callbacks: null,
  133. /**
  134. * APIProperty: selectStyle
  135. * {Object} Hash of styles
  136. */
  137. selectStyle: null,
  138. /**
  139. * Property: renderIntent
  140. * {String} key used to retrieve the select style from the layer's
  141. * style map.
  142. */
  143. renderIntent: "select",
  144. /**
  145. * Property: handlers
  146. * {Object} Object with references to multiple <OpenLayers.Handler>
  147. * instances.
  148. */
  149. handlers: null,
  150. /**
  151. * Constructor: OpenLayers.Control.SelectFeature
  152. * Create a new control for selecting features.
  153. *
  154. * Parameters:
  155. * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
  156. * layer(s) this control will select features from.
  157. * options - {Object}
  158. */
  159. initialize: function(layers, options) {
  160. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  161. if(this.scope === null) {
  162. this.scope = this;
  163. }
  164. this.initLayer(layers);
  165. var callbacks = {
  166. click: this.clickFeature,
  167. clickout: this.clickoutFeature
  168. };
  169. if (this.hover) {
  170. callbacks.over = this.overFeature;
  171. callbacks.out = this.outFeature;
  172. }
  173. this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
  174. this.handlers = {
  175. feature: new OpenLayers.Handler.Feature(
  176. this, this.layer, this.callbacks,
  177. {geometryTypes: this.geometryTypes}
  178. )
  179. };
  180. if (this.box) {
  181. this.handlers.box = new OpenLayers.Handler.Box(
  182. this, {done: this.selectBox},
  183. {boxDivClassName: "olHandlerBoxSelectFeature"}
  184. );
  185. }
  186. },
  187. /**
  188. * Method: initLayer
  189. * Assign the layer property. If layers is an array, we need to use
  190. * a RootContainer.
  191. *
  192. * Parameters:
  193. * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
  194. */
  195. initLayer: function(layers) {
  196. if(OpenLayers.Util.isArray(layers)) {
  197. this.layers = layers;
  198. this.layer = new OpenLayers.Layer.Vector.RootContainer(
  199. this.id + "_container", {
  200. layers: layers
  201. }
  202. );
  203. } else {
  204. this.layer = layers;
  205. }
  206. },
  207. /**
  208. * Method: destroy
  209. */
  210. destroy: function() {
  211. if(this.active && this.layers) {
  212. this.map.removeLayer(this.layer);
  213. }
  214. OpenLayers.Control.prototype.destroy.apply(this, arguments);
  215. if(this.layers) {
  216. this.layer.destroy();
  217. }
  218. },
  219. /**
  220. * Method: activate
  221. * Activates the control.
  222. *
  223. * Returns:
  224. * {Boolean} The control was effectively activated.
  225. */
  226. activate: function () {
  227. if (!this.active) {
  228. if(this.layers) {
  229. this.map.addLayer(this.layer);
  230. }
  231. this.handlers.feature.activate();
  232. if(this.box && this.handlers.box) {
  233. this.handlers.box.activate();
  234. }
  235. }
  236. return OpenLayers.Control.prototype.activate.apply(
  237. this, arguments
  238. );
  239. },
  240. /**
  241. * Method: deactivate
  242. * Deactivates the control.
  243. *
  244. * Returns:
  245. * {Boolean} The control was effectively deactivated.
  246. */
  247. deactivate: function () {
  248. if (this.active) {
  249. this.handlers.feature.deactivate();
  250. if(this.handlers.box) {
  251. this.handlers.box.deactivate();
  252. }
  253. if(this.layers) {
  254. this.map.removeLayer(this.layer);
  255. }
  256. }
  257. return OpenLayers.Control.prototype.deactivate.apply(
  258. this, arguments
  259. );
  260. },
  261. /**
  262. * Method: unselectAll
  263. * Unselect all selected features. To unselect all except for a single
  264. * feature, set the options.except property to the feature.
  265. *
  266. * Parameters:
  267. * options - {Object} Optional configuration object.
  268. */
  269. unselectAll: function(options) {
  270. // we'll want an option to supress notification here
  271. var layers = this.layers || [this.layer],
  272. layer, feature, l, numExcept;
  273. for(l=0; l<layers.length; ++l) {
  274. layer = layers[l];
  275. numExcept = 0;
  276. //layer.selectedFeatures is null when layer is destroyed and
  277. //one of it's preremovelayer listener calls setLayer
  278. //with another layer on this control
  279. if(layer.selectedFeatures != null) {
  280. while(layer.selectedFeatures.length > numExcept) {
  281. feature = layer.selectedFeatures[numExcept];
  282. if(!options || options.except != feature) {
  283. this.unselect(feature);
  284. } else {
  285. ++numExcept;
  286. }
  287. }
  288. }
  289. }
  290. },
  291. /**
  292. * Method: clickFeature
  293. * Called on click in a feature
  294. * Only responds if this.hover is false.
  295. *
  296. * Parameters:
  297. * feature - {<OpenLayers.Feature.Vector>}
  298. */
  299. clickFeature: function(feature) {
  300. if(!this.hover) {
  301. var selected = (OpenLayers.Util.indexOf(
  302. feature.layer.selectedFeatures, feature) > -1);
  303. if(selected) {
  304. if(this.toggleSelect()) {
  305. this.unselect(feature);
  306. } else if(!this.multipleSelect()) {
  307. this.unselectAll({except: feature});
  308. }
  309. } else {
  310. if(!this.multipleSelect()) {
  311. this.unselectAll({except: feature});
  312. }
  313. this.select(feature);
  314. }
  315. }
  316. },
  317. /**
  318. * Method: multipleSelect
  319. * Allow for multiple selected features based on <multiple> property and
  320. * <multipleKey> event modifier.
  321. *
  322. * Returns:
  323. * {Boolean} Allow for multiple selected features.
  324. */
  325. multipleSelect: function() {
  326. return this.multiple || (this.handlers.feature.evt &&
  327. this.handlers.feature.evt[this.multipleKey]);
  328. },
  329. /**
  330. * Method: toggleSelect
  331. * Event should toggle the selected state of a feature based on <toggle>
  332. * property and <toggleKey> event modifier.
  333. *
  334. * Returns:
  335. * {Boolean} Toggle the selected state of a feature.
  336. */
  337. toggleSelect: function() {
  338. return this.toggle || (this.handlers.feature.evt &&
  339. this.handlers.feature.evt[this.toggleKey]);
  340. },
  341. /**
  342. * Method: clickoutFeature
  343. * Called on click outside a previously clicked (selected) feature.
  344. * Only responds if this.hover is false.
  345. *
  346. * Parameters:
  347. * feature - {<OpenLayers.Vector.Feature>}
  348. */
  349. clickoutFeature: function(feature) {
  350. if(!this.hover && this.clickout) {
  351. this.unselectAll();
  352. }
  353. },
  354. /**
  355. * Method: overFeature
  356. * Called on over a feature.
  357. * Only responds if this.hover is true.
  358. *
  359. * Parameters:
  360. * feature - {<OpenLayers.Feature.Vector>}
  361. */
  362. overFeature: function(feature) {
  363. var layer = feature.layer;
  364. if(this.hover) {
  365. if(this.highlightOnly) {
  366. this.highlight(feature);
  367. } else if(OpenLayers.Util.indexOf(
  368. layer.selectedFeatures, feature) == -1) {
  369. this.select(feature);
  370. }
  371. }
  372. },
  373. /**
  374. * Method: outFeature
  375. * Called on out of a selected feature.
  376. * Only responds if this.hover is true.
  377. *
  378. * Parameters:
  379. * feature - {<OpenLayers.Feature.Vector>}
  380. */
  381. outFeature: function(feature) {
  382. if(this.hover) {
  383. if(this.highlightOnly) {
  384. // we do nothing if we're not the last highlighter of the
  385. // feature
  386. if(feature._lastHighlighter == this.id) {
  387. // if another select control had highlighted the feature before
  388. // we did it ourself then we use that control to highlight the
  389. // feature as it was before we highlighted it, else we just
  390. // unhighlight it
  391. if(feature._prevHighlighter &&
  392. feature._prevHighlighter != this.id) {
  393. delete feature._lastHighlighter;
  394. var control = this.map.getControl(
  395. feature._prevHighlighter);
  396. if(control) {
  397. control.highlight(feature);
  398. }
  399. } else {
  400. this.unhighlight(feature);
  401. }
  402. }
  403. } else {
  404. this.unselect(feature);
  405. }
  406. }
  407. },
  408. /**
  409. * Method: highlight
  410. * Redraw feature with the select style.
  411. *
  412. * Parameters:
  413. * feature - {<OpenLayers.Feature.Vector>}
  414. */
  415. highlight: function(feature) {
  416. var layer = feature.layer;
  417. var cont = this.events.triggerEvent("beforefeaturehighlighted", {
  418. feature : feature
  419. });
  420. if(cont !== false) {
  421. feature._prevHighlighter = feature._lastHighlighter;
  422. feature._lastHighlighter = this.id;
  423. var style = this.selectStyle || this.renderIntent;
  424. layer.drawFeature(feature, style);
  425. this.events.triggerEvent("featurehighlighted", {feature : feature});
  426. }
  427. },
  428. /**
  429. * Method: unhighlight
  430. * Redraw feature with the "default" style
  431. *
  432. * Parameters:
  433. * feature - {<OpenLayers.Feature.Vector>}
  434. */
  435. unhighlight: function(feature) {
  436. var layer = feature.layer;
  437. // three cases:
  438. // 1. there's no other highlighter, in that case _prev is undefined,
  439. // and we just need to undef _last
  440. // 2. another control highlighted the feature after we did it, in
  441. // that case _last references this other control, and we just
  442. // need to undef _prev
  443. // 3. another control highlighted the feature before we did it, in
  444. // that case _prev references this other control, and we need to
  445. // set _last to _prev and undef _prev
  446. if(feature._prevHighlighter == undefined) {
  447. delete feature._lastHighlighter;
  448. } else if(feature._prevHighlighter == this.id) {
  449. delete feature._prevHighlighter;
  450. } else {
  451. feature._lastHighlighter = feature._prevHighlighter;
  452. delete feature._prevHighlighter;
  453. }
  454. layer.drawFeature(feature, feature.style || feature.layer.style ||
  455. "default");
  456. this.events.triggerEvent("featureunhighlighted", {feature : feature});
  457. },
  458. /**
  459. * Method: select
  460. * Add feature to the layer's selectedFeature array, render the feature as
  461. * selected, and call the onSelect function.
  462. *
  463. * Parameters:
  464. * feature - {<OpenLayers.Feature.Vector>}
  465. */
  466. select: function(feature) {
  467. var cont = this.onBeforeSelect.call(this.scope, feature);
  468. var layer = feature.layer;
  469. if(cont !== false) {
  470. cont = layer.events.triggerEvent("beforefeatureselected", {
  471. feature: feature
  472. });
  473. if(cont !== false) {
  474. layer.selectedFeatures.push(feature);
  475. this.highlight(feature);
  476. // if the feature handler isn't involved in the feature
  477. // selection (because the box handler is used or the
  478. // feature is selected programatically) we fake the
  479. // feature handler to allow unselecting on click
  480. if(!this.handlers.feature.lastFeature) {
  481. this.handlers.feature.lastFeature = layer.selectedFeatures[0];
  482. }
  483. layer.events.triggerEvent("featureselected", {feature: feature});
  484. this.onSelect.call(this.scope, feature);
  485. }
  486. }
  487. },
  488. /**
  489. * Method: unselect
  490. * Remove feature from the layer's selectedFeature array, render the feature as
  491. * normal, and call the onUnselect function.
  492. *
  493. * Parameters:
  494. * feature - {<OpenLayers.Feature.Vector>}
  495. */
  496. unselect: function(feature) {
  497. var layer = feature.layer;
  498. // Store feature style for restoration later
  499. this.unhighlight(feature);
  500. OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
  501. layer.events.triggerEvent("featureunselected", {feature: feature});
  502. this.onUnselect.call(this.scope, feature);
  503. },
  504. /**
  505. * Method: selectBox
  506. * Callback from the handlers.box set up when <box> selection is true
  507. * on.
  508. *
  509. * Parameters:
  510. * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
  511. */
  512. selectBox: function(position) {
  513. if (position instanceof OpenLayers.Bounds) {
  514. var minXY = this.map.getLonLatFromPixel({
  515. x: position.left,
  516. y: position.bottom
  517. });
  518. var maxXY = this.map.getLonLatFromPixel({
  519. x: position.right,
  520. y: position.top
  521. });
  522. var bounds = new OpenLayers.Bounds(
  523. minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
  524. );
  525. // if multiple is false, first deselect currently selected features
  526. if (!this.multipleSelect()) {
  527. this.unselectAll();
  528. }
  529. // because we're using a box, we consider we want multiple selection
  530. var prevMultiple = this.multiple;
  531. this.multiple = true;
  532. var layers = this.layers || [this.layer];
  533. this.events.triggerEvent("boxselectionstart", {layers: layers});
  534. var layer;
  535. for(var l=0; l<layers.length; ++l) {
  536. layer = layers[l];
  537. for(var i=0, len = layer.features.length; i<len; ++i) {
  538. var feature = layer.features[i];
  539. // check if the feature is displayed
  540. if (!feature.getVisibility()) {
  541. continue;
  542. }
  543. if (this.geometryTypes == null || OpenLayers.Util.indexOf(
  544. this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
  545. if (bounds.toGeometry().intersects(feature.geometry)) {
  546. if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
  547. this.select(feature);
  548. }
  549. }
  550. }
  551. }
  552. }
  553. this.multiple = prevMultiple;
  554. this.events.triggerEvent("boxselectionend", {layers: layers});
  555. }
  556. },
  557. /**
  558. * Method: setMap
  559. * Set the map property for the control.
  560. *
  561. * Parameters:
  562. * map - {<OpenLayers.Map>}
  563. */
  564. setMap: function(map) {
  565. this.handlers.feature.setMap(map);
  566. if (this.box) {
  567. this.handlers.box.setMap(map);
  568. }
  569. OpenLayers.Control.prototype.setMap.apply(this, arguments);
  570. },
  571. /**
  572. * APIMethod: setLayer
  573. * Attach a new layer to the control, overriding any existing layers.
  574. *
  575. * Parameters:
  576. * layers - Array of {<OpenLayers.Layer.Vector>} or a single
  577. * {<OpenLayers.Layer.Vector>}
  578. */
  579. setLayer: function(layers) {
  580. var isActive = this.active;
  581. this.unselectAll();
  582. this.deactivate();
  583. if(this.layers) {
  584. this.layer.destroy();
  585. this.layers = null;
  586. }
  587. this.initLayer(layers);
  588. this.handlers.feature.layer = this.layer;
  589. if (isActive) {
  590. this.activate();
  591. }
  592. },
  593. CLASS_NAME: "OpenLayers.Control.SelectFeature"
  594. });