Feature.js 13 KB

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