Click.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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.Click
  10. * A handler for mouse clicks. The intention of this handler is to give
  11. * controls more flexibility with handling clicks. Browsers trigger
  12. * click events twice for a double-click. In addition, the mousedown,
  13. * mousemove, mouseup sequence fires a click event. With this handler,
  14. * controls can decide whether to ignore clicks associated with a double
  15. * click. By setting a <pixelTolerance>, controls can also ignore clicks
  16. * that include a drag. Create a new instance with the
  17. * <OpenLayers.Handler.Click> constructor.
  18. *
  19. * Inherits from:
  20. * - <OpenLayers.Handler>
  21. */
  22. OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
  23. /**
  24. * APIProperty: delay
  25. * {Number} Number of milliseconds between clicks before the event is
  26. * considered a double-click.
  27. */
  28. delay: 300,
  29. /**
  30. * APIProperty: single
  31. * {Boolean} Handle single clicks. Default is true. If false, clicks
  32. * will not be reported. If true, single-clicks will be reported.
  33. */
  34. single: true,
  35. /**
  36. * APIProperty: double
  37. * {Boolean} Handle double-clicks. Default is false.
  38. */
  39. 'double': false,
  40. /**
  41. * APIProperty: pixelTolerance
  42. * {Number} Maximum number of pixels between mouseup and mousedown for an
  43. * event to be considered a click. Default is 0. If set to an
  44. * integer value, clicks with a drag greater than the value will be
  45. * ignored. This property can only be set when the handler is
  46. * constructed.
  47. */
  48. pixelTolerance: 0,
  49. /**
  50. * APIProperty: dblclickTolerance
  51. * {Number} Maximum distance in pixels between clicks for a sequence of
  52. * events to be considered a double click. Default is 13. If the
  53. * distance between two clicks is greater than this value, a double-
  54. * click will not be fired.
  55. */
  56. dblclickTolerance: 13,
  57. /**
  58. * APIProperty: stopSingle
  59. * {Boolean} Stop other listeners from being notified of clicks. Default
  60. * is false. If true, any listeners registered before this one for
  61. * click or rightclick events will not be notified.
  62. */
  63. stopSingle: false,
  64. /**
  65. * APIProperty: stopDouble
  66. * {Boolean} Stop other listeners from being notified of double-clicks.
  67. * Default is false. If true, any click listeners registered before
  68. * this one will not be notified of *any* double-click events.
  69. *
  70. * The one caveat with stopDouble is that given a map with two click
  71. * handlers, one with stopDouble true and the other with stopSingle
  72. * true, the stopSingle handler should be activated last to get
  73. * uniform cross-browser performance. Since IE triggers one click
  74. * with a dblclick and FF triggers two, if a stopSingle handler is
  75. * activated first, all it gets in IE is a single click when the
  76. * second handler stops propagation on the dblclick.
  77. */
  78. stopDouble: false,
  79. /**
  80. * Property: timerId
  81. * {Number} The id of the timeout waiting to clear the <delayedCall>.
  82. */
  83. timerId: null,
  84. /**
  85. * Property: touch
  86. * {Boolean} When a touchstart event is fired, touch will be true and all
  87. * mouse related listeners will do nothing.
  88. */
  89. touch: false,
  90. /**
  91. * Property: down
  92. * {Object} Object that store relevant information about the last
  93. * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
  94. * the average location of the mouse/touch event. Its 'touches'
  95. * property records clientX/clientY of each touches.
  96. */
  97. down: null,
  98. /**
  99. * Property: last
  100. * {Object} Object that store relevant information about the last
  101. * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
  102. * the average location of the mouse/touch event. Its 'touches'
  103. * property records clientX/clientY of each touches.
  104. */
  105. last: null,
  106. /**
  107. * Property: first
  108. * {Object} When waiting for double clicks, this object will store
  109. * information about the first click in a two click sequence.
  110. */
  111. first: null,
  112. /**
  113. * Property: rightclickTimerId
  114. * {Number} The id of the right mouse timeout waiting to clear the
  115. * <delayedEvent>.
  116. */
  117. rightclickTimerId: null,
  118. /**
  119. * Constructor: OpenLayers.Handler.Click
  120. * Create a new click handler.
  121. *
  122. * Parameters:
  123. * control - {<OpenLayers.Control>} The control that is making use of
  124. * this handler. If a handler is being used without a control, the
  125. * handler's setMap method must be overridden to deal properly with
  126. * the map.
  127. * callbacks - {Object} An object with keys corresponding to callbacks
  128. * that will be called by the handler. The callbacks should
  129. * expect to recieve a single argument, the click event.
  130. * Callbacks for 'click' and 'dblclick' are supported.
  131. * options - {Object} Optional object whose properties will be set on the
  132. * handler.
  133. */
  134. initialize: function(control, callbacks, options) {
  135. OpenLayers.Handler.prototype.initialize.apply(this, arguments);
  136. },
  137. /**
  138. * Method: touchstart
  139. * Handle touchstart.
  140. *
  141. * Returns:
  142. * {Boolean} Continue propagating this event.
  143. */
  144. touchstart: function(evt) {
  145. if (!this.touch) {
  146. this.unregisterMouseListeners();
  147. this.touch = true;
  148. }
  149. this.down = this.getEventInfo(evt);
  150. this.last = this.getEventInfo(evt);
  151. return true;
  152. },
  153. /**
  154. * Method: touchmove
  155. * Store position of last move, because touchend event can have
  156. * an empty "touches" property.
  157. *
  158. * Returns:
  159. * {Boolean} Continue propagating this event.
  160. */
  161. touchmove: function(evt) {
  162. this.last = this.getEventInfo(evt);
  163. return true;
  164. },
  165. /**
  166. * Method: touchend
  167. * Correctly set event xy property, and add lastTouches to have
  168. * touches property from last touchstart or touchmove
  169. *
  170. * Returns:
  171. * {Boolean} Continue propagating this event.
  172. */
  173. touchend: function(evt) {
  174. // touchstart may not have been allowed to propagate
  175. if (this.down) {
  176. evt.xy = this.last.xy;
  177. evt.lastTouches = this.last.touches;
  178. this.handleSingle(evt);
  179. this.down = null;
  180. }
  181. return true;
  182. },
  183. /**
  184. * Method: unregisterMouseListeners
  185. * In a touch environment, we don't want to handle mouse events.
  186. */
  187. unregisterMouseListeners: function() {
  188. this.map.events.un({
  189. mousedown: this.mousedown,
  190. mouseup: this.mouseup,
  191. click: this.click,
  192. dblclick: this.dblclick,
  193. scope: this
  194. });
  195. },
  196. /**
  197. * Method: mousedown
  198. * Handle mousedown.
  199. *
  200. * Returns:
  201. * {Boolean} Continue propagating this event.
  202. */
  203. mousedown: function(evt) {
  204. this.down = this.getEventInfo(evt);
  205. this.last = this.getEventInfo(evt);
  206. return true;
  207. },
  208. /**
  209. * Method: mouseup
  210. * Handle mouseup. Installed to support collection of right mouse events.
  211. *
  212. * Returns:
  213. * {Boolean} Continue propagating this event.
  214. */
  215. mouseup: function (evt) {
  216. var propagate = true;
  217. // Collect right mouse clicks from the mouseup
  218. // IE - ignores the second right click in mousedown so using
  219. // mouseup instead
  220. if (this.checkModifiers(evt) && this.control.handleRightClicks &&
  221. OpenLayers.Event.isRightClick(evt)) {
  222. propagate = this.rightclick(evt);
  223. }
  224. return propagate;
  225. },
  226. /**
  227. * Method: rightclick
  228. * Handle rightclick. For a dblrightclick, we get two clicks so we need
  229. * to always register for dblrightclick to properly handle single
  230. * clicks.
  231. *
  232. * Returns:
  233. * {Boolean} Continue propagating this event.
  234. */
  235. rightclick: function(evt) {
  236. if(this.passesTolerance(evt)) {
  237. if(this.rightclickTimerId != null) {
  238. //Second click received before timeout this must be
  239. // a double click
  240. this.clearTimer();
  241. this.callback('dblrightclick', [evt]);
  242. return !this.stopDouble;
  243. } else {
  244. //Set the rightclickTimerId, send evt only if double is
  245. // true else trigger single
  246. var clickEvent = this['double'] ?
  247. OpenLayers.Util.extend({}, evt) :
  248. this.callback('rightclick', [evt]);
  249. var delayedRightCall = OpenLayers.Function.bind(
  250. this.delayedRightCall,
  251. this,
  252. clickEvent
  253. );
  254. this.rightclickTimerId = window.setTimeout(
  255. delayedRightCall, this.delay
  256. );
  257. }
  258. }
  259. return !this.stopSingle;
  260. },
  261. /**
  262. * Method: delayedRightCall
  263. * Sets <rightclickTimerId> to null. And optionally triggers the
  264. * rightclick callback if evt is set.
  265. */
  266. delayedRightCall: function(evt) {
  267. this.rightclickTimerId = null;
  268. if (evt) {
  269. this.callback('rightclick', [evt]);
  270. }
  271. },
  272. /**
  273. * Method: click
  274. * Handle click events from the browser. This is registered as a listener
  275. * for click events and should not be called from other events in this
  276. * handler.
  277. *
  278. * Returns:
  279. * {Boolean} Continue propagating this event.
  280. */
  281. click: function(evt) {
  282. if (!this.last) {
  283. this.last = this.getEventInfo(evt);
  284. }
  285. this.handleSingle(evt);
  286. return !this.stopSingle;
  287. },
  288. /**
  289. * Method: dblclick
  290. * Handle dblclick. For a dblclick, we get two clicks in some browsers
  291. * (FF) and one in others (IE). So we need to always register for
  292. * dblclick to properly handle single clicks. This method is registered
  293. * as a listener for the dblclick browser event. It should *not* be
  294. * called by other methods in this handler.
  295. *
  296. * Returns:
  297. * {Boolean} Continue propagating this event.
  298. */
  299. dblclick: function(evt) {
  300. this.handleDouble(evt);
  301. return !this.stopDouble;
  302. },
  303. /**
  304. * Method: handleDouble
  305. * Handle double-click sequence.
  306. */
  307. handleDouble: function(evt) {
  308. if (this["double"] && this.passesDblclickTolerance(evt)) {
  309. this.callback("dblclick", [evt]);
  310. }
  311. },
  312. /**
  313. * Method: handleSingle
  314. * Handle single click sequence.
  315. */
  316. handleSingle: function(evt) {
  317. if (this.passesTolerance(evt)) {
  318. if (this.timerId != null) {
  319. // already received a click
  320. if (this.last.touches && this.last.touches.length === 1) {
  321. // touch device, no dblclick event - this may be a double
  322. if (this["double"]) {
  323. // on Android don't let the browser zoom on the page
  324. OpenLayers.Event.stop(evt);
  325. }
  326. this.handleDouble(evt);
  327. }
  328. // if we're not in a touch environment we clear the click timer
  329. // if we've got a second touch, we'll get two touchend events
  330. if (!this.last.touches || this.last.touches.length !== 2) {
  331. this.clearTimer();
  332. }
  333. } else {
  334. // remember the first click info so we can compare to the second
  335. this.first = this.getEventInfo(evt);
  336. // set the timer, send evt only if single is true
  337. //use a clone of the event object because it will no longer
  338. //be a valid event object in IE in the timer callback
  339. var clickEvent = this.single ?
  340. OpenLayers.Util.extend({}, evt) : null;
  341. this.queuePotentialClick(clickEvent);
  342. }
  343. }
  344. },
  345. /**
  346. * Method: queuePotentialClick
  347. * This method is separated out largely to make testing easier (so we
  348. * don't have to override window.setTimeout)
  349. */
  350. queuePotentialClick: function(evt) {
  351. this.timerId = window.setTimeout(
  352. OpenLayers.Function.bind(this.delayedCall, this, evt),
  353. this.delay
  354. );
  355. },
  356. /**
  357. * Method: passesTolerance
  358. * Determine whether the event is within the optional pixel tolerance. Note
  359. * that the pixel tolerance check only works if mousedown events get to
  360. * the listeners registered here. If they are stopped by other elements,
  361. * the <pixelTolerance> will have no effect here (this method will always
  362. * return true).
  363. *
  364. * Returns:
  365. * {Boolean} The click is within the pixel tolerance (if specified).
  366. */
  367. passesTolerance: function(evt) {
  368. var passes = true;
  369. if (this.pixelTolerance != null && this.down && this.down.xy) {
  370. passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
  371. // for touch environments, we also enforce that all touches
  372. // start and end within the given tolerance to be considered a click
  373. if (passes && this.touch &&
  374. this.down.touches.length === this.last.touches.length) {
  375. // the touchend event doesn't come with touches, so we check
  376. // down and last
  377. for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
  378. if (this.getTouchDistance(
  379. this.down.touches[i],
  380. this.last.touches[i]
  381. ) > this.pixelTolerance) {
  382. passes = false;
  383. break;
  384. }
  385. }
  386. }
  387. }
  388. return passes;
  389. },
  390. /**
  391. * Method: getTouchDistance
  392. *
  393. * Returns:
  394. * {Boolean} The pixel displacement between two touches.
  395. */
  396. getTouchDistance: function(from, to) {
  397. return Math.sqrt(
  398. Math.pow(from.clientX - to.clientX, 2) +
  399. Math.pow(from.clientY - to.clientY, 2)
  400. );
  401. },
  402. /**
  403. * Method: passesDblclickTolerance
  404. * Determine whether the event is within the optional double-cick pixel
  405. * tolerance.
  406. *
  407. * Returns:
  408. * {Boolean} The click is within the double-click pixel tolerance.
  409. */
  410. passesDblclickTolerance: function(evt) {
  411. var passes = true;
  412. if (this.down && this.first) {
  413. passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
  414. }
  415. return passes;
  416. },
  417. /**
  418. * Method: clearTimer
  419. * Clear the timer and set <timerId> to null.
  420. */
  421. clearTimer: function() {
  422. if (this.timerId != null) {
  423. window.clearTimeout(this.timerId);
  424. this.timerId = null;
  425. }
  426. if (this.rightclickTimerId != null) {
  427. window.clearTimeout(this.rightclickTimerId);
  428. this.rightclickTimerId = null;
  429. }
  430. },
  431. /**
  432. * Method: delayedCall
  433. * Sets <timerId> to null. And optionally triggers the click callback if
  434. * evt is set.
  435. */
  436. delayedCall: function(evt) {
  437. this.timerId = null;
  438. if (evt) {
  439. this.callback("click", [evt]);
  440. }
  441. },
  442. /**
  443. * Method: getEventInfo
  444. * This method allows us to store event information without storing the
  445. * actual event. In touch devices (at least), the same event is
  446. * modified between touchstart, touchmove, and touchend.
  447. *
  448. * Returns:
  449. * {Object} An object with event related info.
  450. */
  451. getEventInfo: function(evt) {
  452. var touches;
  453. if (evt.touches) {
  454. var len = evt.touches.length;
  455. touches = new Array(len);
  456. var touch;
  457. for (var i=0; i<len; i++) {
  458. touch = evt.touches[i];
  459. touches[i] = {
  460. clientX: touch.clientX,
  461. clientY: touch.clientY
  462. };
  463. }
  464. }
  465. return {
  466. xy: evt.xy,
  467. touches: touches
  468. };
  469. },
  470. /**
  471. * APIMethod: deactivate
  472. * Deactivate the handler.
  473. *
  474. * Returns:
  475. * {Boolean} The handler was successfully deactivated.
  476. */
  477. deactivate: function() {
  478. var deactivated = false;
  479. if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
  480. this.clearTimer();
  481. this.down = null;
  482. this.first = null;
  483. this.last = null;
  484. this.touch = false;
  485. deactivated = true;
  486. }
  487. return deactivated;
  488. },
  489. CLASS_NAME: "OpenLayers.Handler.Click"
  490. });