Feature.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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/Handler.js
  7. */
  8. /**
  9. * Class: OpenLayers.Handler.Feature
  10. * Handler to respond to mouse events related to a drawn feature. Callbacks
  11. * with the following keys will be notified of the following events
  12. * associated with features: click, clickout, over, out, and dblclick.
  13. *
  14. * This handler stops event propagation for mousedown and mouseup if those
  15. * browser events target features that can be selected.
  16. *
  17. * Inherits from:
  18. * - <OpenLayers.Handler>
  19. */
  20. OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
  21. /**
  22. * Property: EVENTMAP
  23. * {Object} A object mapping the browser events to objects with callback
  24. * keys for in and out.
  25. */
  26. EVENTMAP: {
  27. 'click': {'in': 'click', 'out': 'clickout'},
  28. 'mousemove': {'in': 'over', 'out': 'out'},
  29. 'dblclick': {'in': 'dblclick', 'out': null},
  30. 'mousedown': {'in': null, 'out': null},
  31. 'mouseup': {'in': null, 'out': null},
  32. 'touchstart': {'in': 'click', 'out': 'clickout'}
  33. },
  34. /**
  35. * Property: feature
  36. * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
  37. */
  38. feature: null,
  39. /**
  40. * Property: lastFeature
  41. * {<OpenLayers.Feature.Vector>} The last feature that was handled.
  42. */
  43. lastFeature: null,
  44. /**
  45. * Property: down
  46. * {<OpenLayers.Pixel>} The location of the last mousedown.
  47. */
  48. down: null,
  49. /**
  50. * Property: up
  51. * {<OpenLayers.Pixel>} The location of the last mouseup.
  52. */
  53. up: null,
  54. /**
  55. * Property: clickTolerance
  56. * {Number} The number of pixels the mouse can move between mousedown
  57. * and mouseup for the event to still be considered a click.
  58. * Dragging the map should not trigger the click and clickout callbacks
  59. * unless the map is moved by less than this tolerance. Defaults to 4.
  60. */
  61. clickTolerance: 4,
  62. /**
  63. * Property: geometryTypes
  64. * To restrict dragging to a limited set of geometry types, send a list
  65. * of strings corresponding to the geometry class names.
  66. *
  67. * @type Array(String)
  68. */
  69. geometryTypes: null,
  70. /**
  71. * Property: stopClick
  72. * {Boolean} If stopClick is set to true, handled clicks do not
  73. * propagate to other click listeners. Otherwise, handled clicks
  74. * do propagate. Unhandled clicks always propagate, whatever the
  75. * value of stopClick. Defaults to true.
  76. */
  77. stopClick: true,
  78. /**
  79. * Property: stopDown
  80. * {Boolean} If stopDown is set to true, handled mousedowns do not
  81. * propagate to other mousedown listeners. Otherwise, handled
  82. * mousedowns do propagate. Unhandled mousedowns always propagate,
  83. * whatever the value of stopDown. Defaults to true.
  84. */
  85. stopDown: true,
  86. /**
  87. * Property: stopUp
  88. * {Boolean} If stopUp is set to true, handled mouseups do not
  89. * propagate to other mouseup listeners. Otherwise, handled mouseups
  90. * do propagate. Unhandled mouseups always propagate, whatever the
  91. * value of stopUp. Defaults to false.
  92. */
  93. stopUp: false,
  94. /**
  95. * Constructor: OpenLayers.Handler.Feature
  96. *
  97. * Parameters:
  98. * control - {<OpenLayers.Control>}
  99. * layer - {<OpenLayers.Layer.Vector>}
  100. * callbacks - {Object} An object with a 'over' property whos value is
  101. * a function to be called when the mouse is over a feature. The
  102. * callback should expect to recieve a single argument, the feature.
  103. * options - {Object}
  104. */
  105. initialize: function(control, layer, callbacks, options) {
  106. OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
  107. this.layer = layer;
  108. },
  109. /**
  110. * Method: touchstart
  111. * Handle touchstart events
  112. *
  113. * Parameters:
  114. * evt - {Event}
  115. *
  116. * Returns:
  117. * {Boolean} Let the event propagate.
  118. */
  119. touchstart: function(evt) {
  120. this.startTouch();
  121. return OpenLayers.Event.isMultiTouch(evt) ?
  122. true : this.mousedown(evt);
  123. },
  124. /**
  125. * Method: touchmove
  126. * Handle touchmove events. We just prevent the browser default behavior,
  127. * for Android Webkit not to select text when moving the finger after
  128. * selecting a feature.
  129. *
  130. * Parameters:
  131. * evt - {Event}
  132. */
  133. touchmove: function(evt) {
  134. OpenLayers.Event.preventDefault(evt);
  135. },
  136. /**
  137. * Method: mousedown
  138. * Handle mouse down. Stop propagation if a feature is targeted by this
  139. * event (stops map dragging during feature selection).
  140. *
  141. * Parameters:
  142. * evt - {Event}
  143. */
  144. mousedown: function(evt) {
  145. // Feature selection is only done with a left click. Other handlers may stop the
  146. // propagation of left-click mousedown events but not right-click mousedown events.
  147. // This mismatch causes problems when comparing the location of the down and up
  148. // events in the click function so it is important ignore right-clicks.
  149. if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
  150. this.down = evt.xy;
  151. }
  152. return this.handle(evt) ? !this.stopDown : true;
  153. },
  154. /**
  155. * Method: mouseup
  156. * Handle mouse up. Stop propagation if a feature is targeted by this
  157. * event.
  158. *
  159. * Parameters:
  160. * evt - {Event}
  161. */
  162. mouseup: function(evt) {
  163. this.up = evt.xy;
  164. return this.handle(evt) ? !this.stopUp : true;
  165. },
  166. /**
  167. * Method: click
  168. * Handle click. Call the "click" callback if click on a feature,
  169. * or the "clickout" callback if click outside any feature.
  170. *
  171. * Parameters:
  172. * evt - {Event}
  173. *
  174. * Returns:
  175. * {Boolean}
  176. */
  177. click: function(evt) {
  178. return this.handle(evt) ? !this.stopClick : true;
  179. },
  180. /**
  181. * Method: mousemove
  182. * Handle mouse moves. Call the "over" callback if moving in to a feature,
  183. * or the "out" callback if moving out of a feature.
  184. *
  185. * Parameters:
  186. * evt - {Event}
  187. *
  188. * Returns:
  189. * {Boolean}
  190. */
  191. mousemove: function(evt) {
  192. if (!this.callbacks['over'] && !this.callbacks['out']) {
  193. return true;
  194. }
  195. this.handle(evt);
  196. return true;
  197. },
  198. /**
  199. * Method: dblclick
  200. * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
  201. *
  202. * Parameters:
  203. * evt - {Event}
  204. *
  205. * Returns:
  206. * {Boolean}
  207. */
  208. dblclick: function(evt) {
  209. return !this.handle(evt);
  210. },
  211. /**
  212. * Method: geometryTypeMatches
  213. * Return true if the geometry type of the passed feature matches
  214. * one of the geometry types in the geometryTypes array.
  215. *
  216. * Parameters:
  217. * feature - {<OpenLayers.Vector.Feature>}
  218. *
  219. * Returns:
  220. * {Boolean}
  221. */
  222. geometryTypeMatches: function(feature) {
  223. return this.geometryTypes == null ||
  224. OpenLayers.Util.indexOf(this.geometryTypes,
  225. feature.geometry.CLASS_NAME) > -1;
  226. },
  227. /**
  228. * Method: handle
  229. *
  230. * Parameters:
  231. * evt - {Event}
  232. *
  233. * Returns:
  234. * {Boolean} The event occurred over a relevant feature.
  235. */
  236. handle: function(evt) {
  237. if(this.feature && !this.feature.layer) {
  238. // feature has been destroyed
  239. this.feature = null;
  240. }
  241. var type = evt.type;
  242. var handled = false;
  243. var previouslyIn = !!(this.feature); // previously in a feature
  244. var click = (type == "click" || type == "dblclick" || type == "touchstart");
  245. this.feature = this.layer.getFeatureFromEvent(evt);
  246. if(this.feature && !this.feature.layer) {
  247. // feature has been destroyed
  248. this.feature = null;
  249. }
  250. if(this.lastFeature && !this.lastFeature.layer) {
  251. // last feature has been destroyed
  252. this.lastFeature = null;
  253. }
  254. if(this.feature) {
  255. if(type === "touchstart") {
  256. // stop the event to prevent Android Webkit from
  257. // "flashing" the map div
  258. OpenLayers.Event.preventDefault(evt);
  259. }
  260. var inNew = (this.feature != this.lastFeature);
  261. if(this.geometryTypeMatches(this.feature)) {
  262. // in to a feature
  263. if(previouslyIn && inNew) {
  264. // out of last feature and in to another
  265. if(this.lastFeature) {
  266. this.triggerCallback(type, 'out', [this.lastFeature]);
  267. }
  268. this.triggerCallback(type, 'in', [this.feature]);
  269. } else if(!previouslyIn || click) {
  270. // in feature for the first time
  271. this.triggerCallback(type, 'in', [this.feature]);
  272. }
  273. this.lastFeature = this.feature;
  274. handled = true;
  275. } else {
  276. // not in to a feature
  277. if(this.lastFeature && (previouslyIn && inNew || click)) {
  278. // out of last feature for the first time
  279. this.triggerCallback(type, 'out', [this.lastFeature]);
  280. }
  281. // next time the mouse goes in a feature whose geometry type
  282. // doesn't match we don't want to call the 'out' callback
  283. // again, so let's set this.feature to null so that
  284. // previouslyIn will evaluate to false the next time
  285. // we enter handle. Yes, a bit hackish...
  286. this.feature = null;
  287. }
  288. } else if(this.lastFeature && (previouslyIn || click)) {
  289. this.triggerCallback(type, 'out', [this.lastFeature]);
  290. }
  291. return handled;
  292. },
  293. /**
  294. * Method: triggerCallback
  295. * Call the callback keyed in the event map with the supplied arguments.
  296. * For click and clickout, the <clickTolerance> is checked first.
  297. *
  298. * Parameters:
  299. * type - {String}
  300. */
  301. triggerCallback: function(type, mode, args) {
  302. var key = this.EVENTMAP[type][mode];
  303. if(key) {
  304. if(type == 'click' && this.up && this.down) {
  305. // for click/clickout, only trigger callback if tolerance is met
  306. var dpx = Math.sqrt(
  307. Math.pow(this.up.x - this.down.x, 2) +
  308. Math.pow(this.up.y - this.down.y, 2)
  309. );
  310. if(dpx <= this.clickTolerance) {
  311. this.callback(key, args);
  312. }
  313. // we're done with this set of events now: clear the cached
  314. // positions so we can't trip over them later (this can occur
  315. // if one of the up/down events gets eaten before it gets to us
  316. // but we still get the click)
  317. this.up = this.down = null;
  318. } else {
  319. this.callback(key, args);
  320. }
  321. }
  322. },
  323. /**
  324. * Method: activate
  325. * Turn on the handler. Returns false if the handler was already active.
  326. *
  327. * Returns:
  328. * {Boolean}
  329. */
  330. activate: function() {
  331. var activated = false;
  332. if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
  333. this.moveLayerToTop();
  334. this.map.events.on({
  335. "removelayer": this.handleMapEvents,
  336. "changelayer": this.handleMapEvents,
  337. scope: this
  338. });
  339. activated = true;
  340. }
  341. return activated;
  342. },
  343. /**
  344. * Method: deactivate
  345. * Turn off the handler. Returns false if the handler was already active.
  346. *
  347. * Returns:
  348. * {Boolean}
  349. */
  350. deactivate: function() {
  351. var deactivated = false;
  352. if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
  353. this.moveLayerBack();
  354. this.feature = null;
  355. this.lastFeature = null;
  356. this.down = null;
  357. this.up = null;
  358. this.map.events.un({
  359. "removelayer": this.handleMapEvents,
  360. "changelayer": this.handleMapEvents,
  361. scope: this
  362. });
  363. deactivated = true;
  364. }
  365. return deactivated;
  366. },
  367. /**
  368. * Method: handleMapEvents
  369. *
  370. * Parameters:
  371. * evt - {Object}
  372. */
  373. handleMapEvents: function(evt) {
  374. if (evt.type == "removelayer" || evt.property == "order") {
  375. this.moveLayerToTop();
  376. }
  377. },
  378. /**
  379. * Method: moveLayerToTop
  380. * Moves the layer for this handler to the top, so mouse events can reach
  381. * it.
  382. */
  383. moveLayerToTop: function() {
  384. var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
  385. this.layer.getZIndex()) + 1;
  386. this.layer.setZIndex(index);
  387. },
  388. /**
  389. * Method: moveLayerBack
  390. * Moves the layer back to the position determined by the map's layers
  391. * array.
  392. */
  393. moveLayerBack: function() {
  394. var index = this.layer.getZIndex() - 1;
  395. if (index >= this.map.Z_INDEX_BASE['Feature']) {
  396. this.layer.setZIndex(index);
  397. } else {
  398. this.map.setLayerZIndex(this.layer,
  399. this.map.getLayerIndex(this.layer));
  400. }
  401. },
  402. CLASS_NAME: "OpenLayers.Handler.Feature"
  403. });