SelectFeature.js 20 KB

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