Events.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  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/Util.js
  7. */
  8. /**
  9. * Namespace: OpenLayers.Event
  10. * Utility functions for event handling.
  11. */
  12. OpenLayers.Event = {
  13. /**
  14. * Property: observers
  15. * {Object} A hashtable cache of the event observers. Keyed by
  16. * element._eventCacheID
  17. */
  18. observers: false,
  19. /**
  20. * Constant: KEY_SPACE
  21. * {int}
  22. */
  23. KEY_SPACE: 32,
  24. /**
  25. * Constant: KEY_BACKSPACE
  26. * {int}
  27. */
  28. KEY_BACKSPACE: 8,
  29. /**
  30. * Constant: KEY_TAB
  31. * {int}
  32. */
  33. KEY_TAB: 9,
  34. /**
  35. * Constant: KEY_RETURN
  36. * {int}
  37. */
  38. KEY_RETURN: 13,
  39. /**
  40. * Constant: KEY_ESC
  41. * {int}
  42. */
  43. KEY_ESC: 27,
  44. /**
  45. * Constant: KEY_LEFT
  46. * {int}
  47. */
  48. KEY_LEFT: 37,
  49. /**
  50. * Constant: KEY_UP
  51. * {int}
  52. */
  53. KEY_UP: 38,
  54. /**
  55. * Constant: KEY_RIGHT
  56. * {int}
  57. */
  58. KEY_RIGHT: 39,
  59. /**
  60. * Constant: KEY_DOWN
  61. * {int}
  62. */
  63. KEY_DOWN: 40,
  64. /**
  65. * Constant: KEY_DELETE
  66. * {int}
  67. */
  68. KEY_DELETE: 46,
  69. /**
  70. * Method: element
  71. * Cross browser event element detection.
  72. *
  73. * Parameters:
  74. * event - {Event}
  75. *
  76. * Returns:
  77. * {DOMElement} The element that caused the event
  78. */
  79. element: function(event) {
  80. return event.target || event.srcElement;
  81. },
  82. /**
  83. * Method: isSingleTouch
  84. * Determine whether event was caused by a single touch
  85. *
  86. * Parameters:
  87. * event - {Event}
  88. *
  89. * Returns:
  90. * {Boolean}
  91. */
  92. isSingleTouch: function(event) {
  93. return event.touches && event.touches.length == 1;
  94. },
  95. /**
  96. * Method: isMultiTouch
  97. * Determine whether event was caused by a multi touch
  98. *
  99. * Parameters:
  100. * event - {Event}
  101. *
  102. * Returns:
  103. * {Boolean}
  104. */
  105. isMultiTouch: function(event) {
  106. return event.touches && event.touches.length > 1;
  107. },
  108. /**
  109. * Method: isLeftClick
  110. * Determine whether event was caused by a left click.
  111. *
  112. * Parameters:
  113. * event - {Event}
  114. *
  115. * Returns:
  116. * {Boolean}
  117. */
  118. isLeftClick: function(event) {
  119. return (((event.which) && (event.which == 1)) ||
  120. ((event.button) && (event.button == 1)));
  121. },
  122. /**
  123. * Method: isRightClick
  124. * Determine whether event was caused by a right mouse click.
  125. *
  126. * Parameters:
  127. * event - {Event}
  128. *
  129. * Returns:
  130. * {Boolean}
  131. */
  132. isRightClick: function(event) {
  133. return (((event.which) && (event.which == 3)) ||
  134. ((event.button) && (event.button == 2)));
  135. },
  136. /**
  137. * Method: stop
  138. * Stops an event from propagating.
  139. *
  140. * Parameters:
  141. * event - {Event}
  142. * allowDefault - {Boolean} If true, we stop the event chain but
  143. * still allow the default browser behaviour (text selection,
  144. * radio-button clicking, etc). Default is false.
  145. */
  146. stop: function(event, allowDefault) {
  147. if (!allowDefault) {
  148. OpenLayers.Event.preventDefault(event);
  149. }
  150. if (event.stopPropagation) {
  151. event.stopPropagation();
  152. } else {
  153. event.cancelBubble = true;
  154. }
  155. },
  156. /**
  157. * Method: preventDefault
  158. * Cancels the event if it is cancelable, without stopping further
  159. * propagation of the event.
  160. *
  161. * Parameters:
  162. * event - {Event}
  163. */
  164. preventDefault: function(event) {
  165. if (event.preventDefault) {
  166. event.preventDefault();
  167. } else {
  168. event.returnValue = false;
  169. }
  170. },
  171. /**
  172. * Method: findElement
  173. *
  174. * Parameters:
  175. * event - {Event}
  176. * tagName - {String}
  177. *
  178. * Returns:
  179. * {DOMElement} The first node with the given tagName, starting from the
  180. * node the event was triggered on and traversing the DOM upwards
  181. */
  182. findElement: function(event, tagName) {
  183. var element = OpenLayers.Event.element(event);
  184. while (element.parentNode && (!element.tagName ||
  185. (element.tagName.toUpperCase() != tagName.toUpperCase()))){
  186. element = element.parentNode;
  187. }
  188. return element;
  189. },
  190. /**
  191. * Method: observe
  192. *
  193. * Parameters:
  194. * elementParam - {DOMElement || String}
  195. * name - {String}
  196. * observer - {function}
  197. * useCapture - {Boolean}
  198. */
  199. observe: function(elementParam, name, observer, useCapture) {
  200. var element = OpenLayers.Util.getElement(elementParam);
  201. useCapture = useCapture || false;
  202. if (name == 'keypress' &&
  203. (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  204. || element.attachEvent)) {
  205. name = 'keydown';
  206. }
  207. //if observers cache has not yet been created, create it
  208. if (!this.observers) {
  209. this.observers = {};
  210. }
  211. //if not already assigned, make a new unique cache ID
  212. if (!element._eventCacheID) {
  213. var idPrefix = "eventCacheID_";
  214. if (element.id) {
  215. idPrefix = element.id + "_" + idPrefix;
  216. }
  217. element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
  218. }
  219. var cacheID = element._eventCacheID;
  220. //if there is not yet a hash entry for this element, add one
  221. if (!this.observers[cacheID]) {
  222. this.observers[cacheID] = [];
  223. }
  224. //add a new observer to this element's list
  225. this.observers[cacheID].push({
  226. 'element': element,
  227. 'name': name,
  228. 'observer': observer,
  229. 'useCapture': useCapture
  230. });
  231. //add the actual browser event listener
  232. if (element.addEventListener) {
  233. element.addEventListener(name, observer, useCapture);
  234. } else if (element.attachEvent) {
  235. element.attachEvent('on' + name, observer);
  236. }
  237. },
  238. /**
  239. * Method: stopObservingElement
  240. * Given the id of an element to stop observing, cycle through the
  241. * element's cached observers, calling stopObserving on each one,
  242. * skipping those entries which can no longer be removed.
  243. *
  244. * parameters:
  245. * elementParam - {DOMElement || String}
  246. */
  247. stopObservingElement: function(elementParam) {
  248. var element = OpenLayers.Util.getElement(elementParam);
  249. var cacheID = element._eventCacheID;
  250. this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
  251. },
  252. /**
  253. * Method: _removeElementObservers
  254. *
  255. * Parameters:
  256. * elementObservers - {Array(Object)} Array of (element, name,
  257. * observer, usecapture) objects,
  258. * taken directly from hashtable
  259. */
  260. _removeElementObservers: function(elementObservers) {
  261. if (elementObservers) {
  262. for(var i = elementObservers.length-1; i >= 0; i--) {
  263. var entry = elementObservers[i];
  264. OpenLayers.Event.stopObserving.apply(this, [
  265. entry.element, entry.name, entry.observer, entry.useCapture
  266. ]);
  267. }
  268. }
  269. },
  270. /**
  271. * Method: stopObserving
  272. *
  273. * Parameters:
  274. * elementParam - {DOMElement || String}
  275. * name - {String}
  276. * observer - {function}
  277. * useCapture - {Boolean}
  278. *
  279. * Returns:
  280. * {Boolean} Whether or not the event observer was removed
  281. */
  282. stopObserving: function(elementParam, name, observer, useCapture) {
  283. useCapture = useCapture || false;
  284. var element = OpenLayers.Util.getElement(elementParam);
  285. var cacheID = element._eventCacheID;
  286. if (name == 'keypress') {
  287. if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
  288. element.detachEvent) {
  289. name = 'keydown';
  290. }
  291. }
  292. // find element's entry in this.observers cache and remove it
  293. var foundEntry = false;
  294. var elementObservers = OpenLayers.Event.observers[cacheID];
  295. if (elementObservers) {
  296. // find the specific event type in the element's list
  297. var i=0;
  298. while(!foundEntry && i < elementObservers.length) {
  299. var cacheEntry = elementObservers[i];
  300. if ((cacheEntry.name == name) &&
  301. (cacheEntry.observer == observer) &&
  302. (cacheEntry.useCapture == useCapture)) {
  303. elementObservers.splice(i, 1);
  304. if (elementObservers.length == 0) {
  305. delete OpenLayers.Event.observers[cacheID];
  306. }
  307. foundEntry = true;
  308. break;
  309. }
  310. i++;
  311. }
  312. }
  313. //actually remove the event listener from browser
  314. if (foundEntry) {
  315. if (element.removeEventListener) {
  316. element.removeEventListener(name, observer, useCapture);
  317. } else if (element && element.detachEvent) {
  318. element.detachEvent('on' + name, observer);
  319. }
  320. }
  321. return foundEntry;
  322. },
  323. /**
  324. * Method: unloadCache
  325. * Cycle through all the element entries in the events cache and call
  326. * stopObservingElement on each.
  327. */
  328. unloadCache: function() {
  329. // check for OpenLayers.Event before checking for observers, because
  330. // OpenLayers.Event may be undefined in IE if no map instance was
  331. // created
  332. if (OpenLayers.Event && OpenLayers.Event.observers) {
  333. for (var cacheID in OpenLayers.Event.observers) {
  334. var elementObservers = OpenLayers.Event.observers[cacheID];
  335. OpenLayers.Event._removeElementObservers.apply(this,
  336. [elementObservers]);
  337. }
  338. OpenLayers.Event.observers = false;
  339. }
  340. },
  341. CLASS_NAME: "OpenLayers.Event"
  342. };
  343. /* prevent memory leaks in IE */
  344. OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
  345. /**
  346. * Class: OpenLayers.Events
  347. */
  348. OpenLayers.Events = OpenLayers.Class({
  349. /**
  350. * Constant: BROWSER_EVENTS
  351. * {Array(String)} supported events
  352. */
  353. BROWSER_EVENTS: [
  354. "mouseover", "mouseout",
  355. "mousedown", "mouseup", "mousemove",
  356. "click", "dblclick", "rightclick", "dblrightclick",
  357. "resize", "focus", "blur",
  358. "touchstart", "touchmove", "touchend",
  359. "keydown"
  360. ],
  361. /**
  362. * Property: listeners
  363. * {Object} Hashtable of Array(Function): events listener functions
  364. */
  365. listeners: null,
  366. /**
  367. * Property: object
  368. * {Object} the code object issuing application events
  369. */
  370. object: null,
  371. /**
  372. * Property: element
  373. * {DOMElement} the DOM element receiving browser events
  374. */
  375. element: null,
  376. /**
  377. * Property: eventHandler
  378. * {Function} bound event handler attached to elements
  379. */
  380. eventHandler: null,
  381. /**
  382. * APIProperty: fallThrough
  383. * {Boolean}
  384. */
  385. fallThrough: null,
  386. /**
  387. * APIProperty: includeXY
  388. * {Boolean} Should the .xy property automatically be created for browser
  389. * mouse events? In general, this should be false. If it is true, then
  390. * mouse events will automatically generate a '.xy' property on the
  391. * event object that is passed. (Prior to OpenLayers 2.7, this was true
  392. * by default.) Otherwise, you can call the getMousePosition on the
  393. * relevant events handler on the object available via the 'evt.object'
  394. * property of the evt object. So, for most events, you can call:
  395. * function named(evt) {
  396. * this.xy = this.object.events.getMousePosition(evt)
  397. * }
  398. *
  399. * This option typically defaults to false for performance reasons:
  400. * when creating an events object whose primary purpose is to manage
  401. * relatively positioned mouse events within a div, it may make
  402. * sense to set it to true.
  403. *
  404. * This option is also used to control whether the events object caches
  405. * offsets. If this is false, it will not: the reason for this is that
  406. * it is only expected to be called many times if the includeXY property
  407. * is set to true. If you set this to true, you are expected to clear
  408. * the offset cache manually (using this.clearMouseCache()) if:
  409. * the border of the element changes
  410. * the location of the element in the page changes
  411. */
  412. includeXY: false,
  413. /**
  414. * APIProperty: extensions
  415. * {Object} Event extensions registered with this instance. Keys are
  416. * event types, values are {OpenLayers.Events.*} extension instances or
  417. * {Boolean} for events that an instantiated extension provides in
  418. * addition to the one it was created for.
  419. *
  420. * Extensions create an event in addition to browser events, which usually
  421. * fires when a sequence of browser events is completed. Extensions are
  422. * automatically instantiated when a listener is registered for an event
  423. * provided by an extension.
  424. *
  425. * Extensions are created in the <OpenLayers.Events> namespace using
  426. * <OpenLayers.Class>, and named after the event they provide.
  427. * The constructor receives the target <OpenLayers.Events> instance as
  428. * argument. Extensions that need to capture browser events before they
  429. * propagate can register their listeners events using <register>, with
  430. * {extension: true} as 4th argument.
  431. *
  432. * If an extension creates more than one event, an alias for each event
  433. * type should be created and reference the same class. The constructor
  434. * should set a reference in the target's extensions registry to itself.
  435. *
  436. * Below is a minimal extension that provides the "foostart" and "fooend"
  437. * event types, which replace the native "click" event type if clicked on
  438. * an element with the css class "foo":
  439. *
  440. * (code)
  441. * OpenLayers.Events.foostart = OpenLayers.Class({
  442. * initialize: function(target) {
  443. * this.target = target;
  444. * this.target.register("click", this, this.doStuff, {extension: true});
  445. * // only required if extension provides more than one event type
  446. * this.target.extensions["foostart"] = true;
  447. * this.target.extensions["fooend"] = true;
  448. * },
  449. * destroy: function() {
  450. * var target = this.target;
  451. * target.unregister("click", this, this.doStuff);
  452. * delete this.target;
  453. * // only required if extension provides more than one event type
  454. * delete target.extensions["foostart"];
  455. * delete target.extensions["fooend"];
  456. * },
  457. * doStuff: function(evt) {
  458. * var propagate = true;
  459. * if (OpenLayers.Event.element(evt).className === "foo") {
  460. * propagate = false;
  461. * var target = this.target;
  462. * target.triggerEvent("foostart");
  463. * window.setTimeout(function() {
  464. * target.triggerEvent("fooend");
  465. * }, 1000);
  466. * }
  467. * return propagate;
  468. * }
  469. * });
  470. * // only required if extension provides more than one event type
  471. * OpenLayers.Events.fooend = OpenLayers.Events.foostart;
  472. * (end)
  473. *
  474. */
  475. extensions: null,
  476. /**
  477. * Property: extensionCount
  478. * {Object} Keys are event types (like in <listeners>), values are the
  479. * number of extension listeners for each event type.
  480. */
  481. extensionCount: null,
  482. /**
  483. * Method: clearMouseListener
  484. * A version of <clearMouseCache> that is bound to this instance so that
  485. * it can be used with <OpenLayers.Event.observe> and
  486. * <OpenLayers.Event.stopObserving>.
  487. */
  488. clearMouseListener: null,
  489. /**
  490. * Constructor: OpenLayers.Events
  491. * Construct an OpenLayers.Events object.
  492. *
  493. * Parameters:
  494. * object - {Object} The js object to which this Events object is being added
  495. * element - {DOMElement} A dom element to respond to browser events
  496. * eventTypes - {Array(String)} Deprecated. Array of custom application
  497. * events. A listener may be registered for any named event, regardless
  498. * of the values provided here.
  499. * fallThrough - {Boolean} Allow events to fall through after these have
  500. * been handled?
  501. * options - {Object} Options for the events object.
  502. */
  503. initialize: function (object, element, eventTypes, fallThrough, options) {
  504. OpenLayers.Util.extend(this, options);
  505. this.object = object;
  506. this.fallThrough = fallThrough;
  507. this.listeners = {};
  508. this.extensions = {};
  509. this.extensionCount = {};
  510. this._msTouches = [];
  511. // if a dom element is specified, add a listeners list
  512. // for browser events on the element and register them
  513. if (element != null) {
  514. this.attachToElement(element);
  515. }
  516. },
  517. /**
  518. * APIMethod: destroy
  519. */
  520. destroy: function () {
  521. for (var e in this.extensions) {
  522. if (typeof this.extensions[e] !== "boolean") {
  523. this.extensions[e].destroy();
  524. }
  525. }
  526. this.extensions = null;
  527. if (this.element) {
  528. OpenLayers.Event.stopObservingElement(this.element);
  529. if(this.element.hasScrollEvent) {
  530. OpenLayers.Event.stopObserving(
  531. window, "scroll", this.clearMouseListener
  532. );
  533. }
  534. }
  535. this.element = null;
  536. this.listeners = null;
  537. this.object = null;
  538. this.fallThrough = null;
  539. this.eventHandler = null;
  540. },
  541. /**
  542. * APIMethod: addEventType
  543. * Deprecated. Any event can be triggered without adding it first.
  544. *
  545. * Parameters:
  546. * eventName - {String}
  547. */
  548. addEventType: function(eventName) {
  549. },
  550. /**
  551. * Method: attachToElement
  552. *
  553. * Parameters:
  554. * element - {HTMLDOMElement} a DOM element to attach browser events to
  555. */
  556. attachToElement: function (element) {
  557. if (this.element) {
  558. OpenLayers.Event.stopObservingElement(this.element);
  559. } else {
  560. // keep a bound copy of handleBrowserEvent() so that we can
  561. // pass the same function to both Event.observe() and .stopObserving()
  562. this.eventHandler = OpenLayers.Function.bindAsEventListener(
  563. this.handleBrowserEvent, this
  564. );
  565. // to be used with observe and stopObserving
  566. this.clearMouseListener = OpenLayers.Function.bind(
  567. this.clearMouseCache, this
  568. );
  569. }
  570. this.element = element;
  571. var msTouch = !!window.navigator.msMaxTouchPoints;
  572. var type;
  573. for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
  574. type = this.BROWSER_EVENTS[i];
  575. // register the event cross-browser
  576. OpenLayers.Event.observe(element, type, this.eventHandler
  577. );
  578. if (msTouch && type.indexOf('touch') === 0) {
  579. this.addMsTouchListener(element, type, this.eventHandler);
  580. }
  581. }
  582. // disable dragstart in IE so that mousedown/move/up works normally
  583. OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
  584. },
  585. /**
  586. * APIMethod: on
  587. * Convenience method for registering listeners with a common scope.
  588. * Internally, this method calls <register> as shown in the examples
  589. * below.
  590. *
  591. * Example use:
  592. * (code)
  593. * // register a single listener for the "loadstart" event
  594. * events.on({"loadstart": loadStartListener});
  595. *
  596. * // this is equivalent to the following
  597. * events.register("loadstart", undefined, loadStartListener);
  598. *
  599. * // register multiple listeners to be called with the same `this` object
  600. * events.on({
  601. * "loadstart": loadStartListener,
  602. * "loadend": loadEndListener,
  603. * scope: object
  604. * });
  605. *
  606. * // this is equivalent to the following
  607. * events.register("loadstart", object, loadStartListener);
  608. * events.register("loadend", object, loadEndListener);
  609. * (end)
  610. *
  611. * Parameters:
  612. * object - {Object}
  613. */
  614. on: function(object) {
  615. for(var type in object) {
  616. if(type != "scope" && object.hasOwnProperty(type)) {
  617. this.register(type, object.scope, object[type]);
  618. }
  619. }
  620. },
  621. /**
  622. * APIMethod: register
  623. * Register an event on the events object.
  624. *
  625. * When the event is triggered, the 'func' function will be called, in the
  626. * context of 'obj'. Imagine we were to register an event, specifying an
  627. * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
  628. * context in the callback function will be our Bounds object. This means
  629. * that within our callback function, we can access the properties and
  630. * methods of the Bounds object through the "this" variable. So our
  631. * callback could execute something like:
  632. * : leftStr = "Left: " + this.left;
  633. *
  634. * or
  635. *
  636. * : centerStr = "Center: " + this.getCenterLonLat();
  637. *
  638. * Parameters:
  639. * type - {String} Name of the event to register
  640. * obj - {Object} The object to bind the context to for the callback#.
  641. * If no object is specified, default is the Events's 'object' property.
  642. * func - {Function} The callback function. If no callback is
  643. * specified, this function does nothing.
  644. * priority - {Boolean|Object} If true, adds the new listener to the
  645. * *front* of the events queue instead of to the end.
  646. *
  647. * Valid options for priority:
  648. * extension - {Boolean} If true, then the event will be registered as
  649. * extension event. Extension events are handled before all other
  650. * events.
  651. */
  652. register: function (type, obj, func, priority) {
  653. if (type in OpenLayers.Events && !this.extensions[type]) {
  654. this.extensions[type] = new OpenLayers.Events[type](this);
  655. }
  656. if (func != null) {
  657. if (obj == null) {
  658. obj = this.object;
  659. }
  660. var listeners = this.listeners[type];
  661. if (!listeners) {
  662. listeners = [];
  663. this.listeners[type] = listeners;
  664. this.extensionCount[type] = 0;
  665. }
  666. var listener = {obj: obj, func: func};
  667. if (priority) {
  668. listeners.splice(this.extensionCount[type], 0, listener);
  669. if (typeof priority === "object" && priority.extension) {
  670. this.extensionCount[type]++;
  671. }
  672. } else {
  673. listeners.push(listener);
  674. }
  675. }
  676. },
  677. /**
  678. * APIMethod: registerPriority
  679. * Same as register() but adds the new listener to the *front* of the
  680. * events queue instead of to the end.
  681. *
  682. * TODO: get rid of this in 3.0 - Decide whether listeners should be
  683. * called in the order they were registered or in reverse order.
  684. *
  685. *
  686. * Parameters:
  687. * type - {String} Name of the event to register
  688. * obj - {Object} The object to bind the context to for the callback#.
  689. * If no object is specified, default is the Events's
  690. * 'object' property.
  691. * func - {Function} The callback function. If no callback is
  692. * specified, this function does nothing.
  693. */
  694. registerPriority: function (type, obj, func) {
  695. this.register(type, obj, func, true);
  696. },
  697. /**
  698. * APIMethod: un
  699. * Convenience method for unregistering listeners with a common scope.
  700. * Internally, this method calls <unregister> as shown in the examples
  701. * below.
  702. *
  703. * Example use:
  704. * (code)
  705. * // unregister a single listener for the "loadstart" event
  706. * events.un({"loadstart": loadStartListener});
  707. *
  708. * // this is equivalent to the following
  709. * events.unregister("loadstart", undefined, loadStartListener);
  710. *
  711. * // unregister multiple listeners with the same `this` object
  712. * events.un({
  713. * "loadstart": loadStartListener,
  714. * "loadend": loadEndListener,
  715. * scope: object
  716. * });
  717. *
  718. * // this is equivalent to the following
  719. * events.unregister("loadstart", object, loadStartListener);
  720. * events.unregister("loadend", object, loadEndListener);
  721. * (end)
  722. */
  723. un: function(object) {
  724. for(var type in object) {
  725. if(type != "scope" && object.hasOwnProperty(type)) {
  726. this.unregister(type, object.scope, object[type]);
  727. }
  728. }
  729. },
  730. /**
  731. * APIMethod: unregister
  732. *
  733. * Parameters:
  734. * type - {String}
  735. * obj - {Object} If none specified, defaults to this.object
  736. * func - {Function}
  737. */
  738. unregister: function (type, obj, func) {
  739. if (obj == null) {
  740. obj = this.object;
  741. }
  742. var listeners = this.listeners[type];
  743. if (listeners != null) {
  744. for (var i=0, len=listeners.length; i<len; i++) {
  745. if (listeners[i].obj == obj && listeners[i].func == func) {
  746. listeners.splice(i, 1);
  747. break;
  748. }
  749. }
  750. }
  751. },
  752. /**
  753. * Method: remove
  754. * Remove all listeners for a given event type. If type is not registered,
  755. * does nothing.
  756. *
  757. * Parameters:
  758. * type - {String}
  759. */
  760. remove: function(type) {
  761. if (this.listeners[type] != null) {
  762. this.listeners[type] = [];
  763. }
  764. },
  765. /**
  766. * APIMethod: triggerEvent
  767. * Trigger a specified registered event.
  768. *
  769. * Parameters:
  770. * type - {String}
  771. * evt - {Event || Object} will be passed to the listeners.
  772. *
  773. * Returns:
  774. * {Boolean} The last listener return. If a listener returns false, the
  775. * chain of listeners will stop getting called.
  776. */
  777. triggerEvent: function (type, evt) {
  778. var listeners = this.listeners[type];
  779. // fast path
  780. if(!listeners || listeners.length == 0) {
  781. return undefined;
  782. }
  783. // prep evt object with object & div references
  784. if (evt == null) {
  785. evt = {};
  786. }
  787. evt.object = this.object;
  788. evt.element = this.element;
  789. if(!evt.type) {
  790. evt.type = type;
  791. }
  792. // execute all callbacks registered for specified type
  793. // get a clone of the listeners array to
  794. // allow for splicing during callbacks
  795. listeners = listeners.slice();
  796. var continueChain;
  797. for (var i=0, len=listeners.length; i<len; i++) {
  798. var callback = listeners[i];
  799. // bind the context to callback.obj
  800. continueChain = callback.func.apply(callback.obj, [evt]);
  801. if ((continueChain != undefined) && (continueChain == false)) {
  802. // if callback returns false, execute no more callbacks.
  803. break;
  804. }
  805. }
  806. // don't fall through to other DOM elements
  807. if (!this.fallThrough) {
  808. OpenLayers.Event.stop(evt, true);
  809. }
  810. return continueChain;
  811. },
  812. /**
  813. * Method: handleBrowserEvent
  814. * Basically just a wrapper to the triggerEvent() function, but takes
  815. * care to set a property 'xy' on the event with the current mouse
  816. * position.
  817. *
  818. * Parameters:
  819. * evt - {Event}
  820. */
  821. handleBrowserEvent: function (evt) {
  822. var type = evt.type, listeners = this.listeners[type];
  823. if(!listeners || listeners.length == 0) {
  824. // noone's listening, bail out
  825. return;
  826. }
  827. // add clientX & clientY to all events - corresponds to average x, y
  828. var touches = evt.touches;
  829. if (touches && touches[0]) {
  830. var x = 0;
  831. var y = 0;
  832. var num = touches.length;
  833. var touch;
  834. for (var i=0; i<num; ++i) {
  835. touch = this.getTouchClientXY(touches[i]);
  836. x += touch.clientX;
  837. y += touch.clientY;
  838. }
  839. evt.clientX = x / num;
  840. evt.clientY = y / num;
  841. }
  842. if (this.includeXY) {
  843. evt.xy = this.getMousePosition(evt);
  844. }
  845. this.triggerEvent(type, evt);
  846. },
  847. /**
  848. * Method: getTouchClientXY
  849. * WebKit has a few bugs for clientX/clientY. This method detects them
  850. * and calculate the correct values.
  851. *
  852. * Parameters:
  853. * evt - {Touch} a Touch object from a TouchEvent
  854. *
  855. * Returns:
  856. * {Object} An object with only clientX and clientY properties with the
  857. * calculated values.
  858. */
  859. getTouchClientXY: function (evt) {
  860. // olMochWin is to override window, used for testing
  861. var win = window.olMockWin || window,
  862. winPageX = win.pageXOffset,
  863. winPageY = win.pageYOffset,
  864. x = evt.clientX,
  865. y = evt.clientY;
  866. if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
  867. evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
  868. // iOS4 include scroll offset in clientX/Y
  869. x = x - winPageX;
  870. y = y - winPageY;
  871. } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
  872. // Some Android browsers have totally bogus values for clientX/Y
  873. // when scrolling/zooming a page
  874. x = evt.pageX - winPageX;
  875. y = evt.pageY - winPageY;
  876. }
  877. evt.olClientX = x;
  878. evt.olClientY = y;
  879. return {
  880. clientX: x,
  881. clientY: y
  882. };
  883. },
  884. /**
  885. * APIMethod: clearMouseCache
  886. * Clear cached data about the mouse position. This should be called any
  887. * time the element that events are registered on changes position
  888. * within the page.
  889. */
  890. clearMouseCache: function() {
  891. this.element.scrolls = null;
  892. this.element.lefttop = null;
  893. this.element.offsets = null;
  894. },
  895. /**
  896. * Method: getMousePosition
  897. *
  898. * Parameters:
  899. * evt - {Event}
  900. *
  901. * Returns:
  902. * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
  903. * for offsets
  904. */
  905. getMousePosition: function (evt) {
  906. if (!this.includeXY) {
  907. this.clearMouseCache();
  908. } else if (!this.element.hasScrollEvent) {
  909. OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
  910. this.element.hasScrollEvent = true;
  911. }
  912. if (!this.element.scrolls) {
  913. var viewportElement = OpenLayers.Util.getViewportElement();
  914. this.element.scrolls = [
  915. window.pageXOffset || viewportElement.scrollLeft,
  916. window.pageYOffset || viewportElement.scrollTop
  917. ];
  918. }
  919. if (!this.element.lefttop) {
  920. this.element.lefttop = [
  921. (document.documentElement.clientLeft || 0),
  922. (document.documentElement.clientTop || 0)
  923. ];
  924. }
  925. if (!this.element.offsets) {
  926. this.element.offsets = OpenLayers.Util.pagePosition(this.element);
  927. }
  928. return new OpenLayers.Pixel(
  929. (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
  930. - this.element.lefttop[0],
  931. (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
  932. - this.element.lefttop[1]
  933. );
  934. },
  935. /**
  936. * Method: addMsTouchListener
  937. *
  938. * Parameters:
  939. * element - {DOMElement} The DOM element to register the listener on
  940. * type - {String} The event type
  941. * handler - {Function} the handler
  942. */
  943. addMsTouchListener: function (element, type, handler) {
  944. var eventHandler = this.eventHandler;
  945. var touches = this._msTouches;
  946. function msHandler(evt) {
  947. handler(OpenLayers.Util.applyDefaults({
  948. stopPropagation: function() {
  949. for (var i=touches.length-1; i>=0; --i) {
  950. touches[i].stopPropagation();
  951. }
  952. },
  953. preventDefault: function() {
  954. for (var i=touches.length-1; i>=0; --i) {
  955. touches[i].preventDefault();
  956. }
  957. },
  958. type: type
  959. }, evt));
  960. }
  961. switch (type) {
  962. case 'touchstart':
  963. return this.addMsTouchListenerStart(element, type, msHandler);
  964. case 'touchend':
  965. return this.addMsTouchListenerEnd(element, type, msHandler);
  966. case 'touchmove':
  967. return this.addMsTouchListenerMove(element, type, msHandler);
  968. default:
  969. throw 'Unknown touch event type';
  970. }
  971. },
  972. /**
  973. * Method: addMsTouchListenerStart
  974. *
  975. * Parameters:
  976. * element - {DOMElement} The DOM element to register the listener on
  977. * type - {String} The event type
  978. * handler - {Function} the handler
  979. */
  980. addMsTouchListenerStart: function(element, type, handler) {
  981. var touches = this._msTouches;
  982. var cb = function(e) {
  983. var alreadyInArray = false;
  984. for (var i=0, ii=touches.length; i<ii; ++i) {
  985. if (touches[i].pointerId == e.pointerId) {
  986. alreadyInArray = true;
  987. break;
  988. }
  989. }
  990. if (!alreadyInArray) {
  991. touches.push(e);
  992. }
  993. e.touches = touches.slice();
  994. handler(e);
  995. };
  996. OpenLayers.Event.observe(element, 'MSPointerDown', cb);
  997. // Need to also listen for end events to keep the _msTouches list
  998. // accurate
  999. var internalCb = function(e) {
  1000. for (var i=0, ii=touches.length; i<ii; ++i) {
  1001. if (touches[i].pointerId == e.pointerId) {
  1002. touches.splice(i, 1);
  1003. break;
  1004. }
  1005. }
  1006. };
  1007. OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
  1008. },
  1009. /**
  1010. * Method: addMsTouchListenerMove
  1011. *
  1012. * Parameters:
  1013. * element - {DOMElement} The DOM element to register the listener on
  1014. * type - {String} The event type
  1015. * handler - {Function} the handler
  1016. */
  1017. addMsTouchListenerMove: function (element, type, handler) {
  1018. var touches = this._msTouches;
  1019. var cb = function(e) {
  1020. //Don't fire touch moves when mouse isn't down
  1021. if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
  1022. return;
  1023. }
  1024. if (touches.length == 1 && touches[0].pageX == e.pageX &&
  1025. touches[0].pageY == e.pageY) {
  1026. // don't trigger event when pointer has not moved
  1027. return;
  1028. }
  1029. for (var i=0, ii=touches.length; i<ii; ++i) {
  1030. if (touches[i].pointerId == e.pointerId) {
  1031. touches[i] = e;
  1032. break;
  1033. }
  1034. }
  1035. e.touches = touches.slice();
  1036. handler(e);
  1037. };
  1038. OpenLayers.Event.observe(element, 'MSPointerMove', cb);
  1039. },
  1040. /**
  1041. * Method: addMsTouchListenerEnd
  1042. *
  1043. * Parameters:
  1044. * element - {DOMElement} The DOM element to register the listener on
  1045. * type - {String} The event type
  1046. * handler - {Function} the handler
  1047. */
  1048. addMsTouchListenerEnd: function (element, type, handler) {
  1049. var touches = this._msTouches;
  1050. var cb = function(e) {
  1051. for (var i=0, ii=touches.length; i<ii; ++i) {
  1052. if (touches[i].pointerId == e.pointerId) {
  1053. touches.splice(i, 1);
  1054. break;
  1055. }
  1056. }
  1057. e.touches = touches.slice();
  1058. handler(e);
  1059. };
  1060. OpenLayers.Event.observe(element, 'MSPointerUp', cb);
  1061. },
  1062. CLASS_NAME: "OpenLayers.Events"
  1063. });