on.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. define(["./has!dom-addeventlistener?:./aspect", "./_base/kernel", "./sniff"], function(aspect, dojo, has){
  2. "use strict";
  3. if(has("dom")){ // check to make sure we are in a browser, this module should work anywhere
  4. var major = window.ScriptEngineMajorVersion;
  5. has.add("jscript", major && (major() + ScriptEngineMinorVersion() / 10));
  6. has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this?
  7. has.add("event-stopimmediatepropagation", window.Event && !!window.Event.prototype && !!window.Event.prototype.stopImmediatePropagation);
  8. has.add("event-focusin", function(global, doc, element){
  9. return 'onfocusin' in element;
  10. });
  11. if(has("touch")){
  12. has.add("touch-can-modify-event-delegate", function(){
  13. // This feature test checks whether deleting a property of an event delegate works
  14. // for a touch-enabled device. If it works, event delegation can be used as fallback
  15. // for browsers such as Safari in older iOS where deleting properties of the original
  16. // event does not work.
  17. var EventDelegate = function(){};
  18. EventDelegate.prototype =
  19. document.createEvent("MouseEvents"); // original event
  20. // Attempt to modify a property of an event delegate and check if
  21. // it succeeds. Depending on browsers and on whether dojo/on's
  22. // strict mode is stripped in a Dojo build, there are 3 known behaviors:
  23. // it may either succeed, or raise an error, or fail to set the property
  24. // without raising an error.
  25. try{
  26. var eventDelegate = new EventDelegate;
  27. eventDelegate.target = null;
  28. return eventDelegate.target === null;
  29. }catch(e){
  30. return false; // cannot use event delegation
  31. }
  32. });
  33. }
  34. }
  35. var on = function(target, type, listener, dontFix){
  36. // summary:
  37. // A function that provides core event listening functionality. With this function
  38. // you can provide a target, event type, and listener to be notified of
  39. // future matching events that are fired.
  40. // target: Element|Object
  41. // This is the target object or DOM element that to receive events from
  42. // type: String|Function
  43. // This is the name of the event to listen for or an extension event type.
  44. // listener: Function
  45. // This is the function that should be called when the event fires.
  46. // returns: Object
  47. // An object with a remove() method that can be used to stop listening for this
  48. // event.
  49. // description:
  50. // To listen for "click" events on a button node, we can do:
  51. // | define(["dojo/on"], function(on){
  52. // | on(button, "click", clickHandler);
  53. // | ...
  54. // Evented JavaScript objects can also have their own events.
  55. // | var obj = new Evented;
  56. // | on(obj, "foo", fooHandler);
  57. // And then we could publish a "foo" event:
  58. // | on.emit(obj, "foo", {key: "value"});
  59. // We can use extension events as well. For example, you could listen for a tap gesture:
  60. // | define(["dojo/on", "dojo/gesture/tap", function(on, tap){
  61. // | on(button, tap, tapHandler);
  62. // | ...
  63. // which would trigger fooHandler. Note that for a simple object this is equivalent to calling:
  64. // | obj.onfoo({key:"value"});
  65. // If you use on.emit on a DOM node, it will use native event dispatching when possible.
  66. if(typeof target.on == "function" && typeof type != "function" && !target.nodeType){
  67. // delegate to the target's on() method, so it can handle it's own listening if it wants (unless it
  68. // is DOM node and we may be dealing with jQuery or Prototype's incompatible addition to the
  69. // Element prototype
  70. return target.on(type, listener);
  71. }
  72. // delegate to main listener code
  73. return on.parse(target, type, listener, addListener, dontFix, this);
  74. };
  75. on.pausable = function(target, type, listener, dontFix){
  76. // summary:
  77. // This function acts the same as on(), but with pausable functionality. The
  78. // returned signal object has pause() and resume() functions. Calling the
  79. // pause() method will cause the listener to not be called for future events. Calling the
  80. // resume() method will cause the listener to again be called for future events.
  81. var paused;
  82. var signal = on(target, type, function(){
  83. if(!paused){
  84. return listener.apply(this, arguments);
  85. }
  86. }, dontFix);
  87. signal.pause = function(){
  88. paused = true;
  89. };
  90. signal.resume = function(){
  91. paused = false;
  92. };
  93. return signal;
  94. };
  95. on.once = function(target, type, listener, dontFix){
  96. // summary:
  97. // This function acts the same as on(), but will only call the listener once. The
  98. // listener will be called for the first
  99. // event that takes place and then listener will automatically be removed.
  100. var signal = on(target, type, function(){
  101. // remove this listener
  102. signal.remove();
  103. // proceed to call the listener
  104. return listener.apply(this, arguments);
  105. });
  106. return signal;
  107. };
  108. on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){
  109. if(type.call){
  110. // event handler function
  111. // on(node, touch.press, touchListener);
  112. return type.call(matchesTarget, target, listener);
  113. }
  114. if(type instanceof Array){
  115. // allow an array of event names (or event handler functions)
  116. events = type;
  117. }else if(type.indexOf(",") > -1){
  118. // we allow comma delimited event names, so you can register for multiple events at once
  119. var events = type.split(/\s*,\s*/);
  120. }
  121. if(events){
  122. var handles = [];
  123. var i = 0;
  124. var eventName;
  125. while(eventName = events[i++]){
  126. handles.push(on.parse(target, eventName, listener, addListener, dontFix, matchesTarget));
  127. }
  128. handles.remove = function(){
  129. for(var i = 0; i < handles.length; i++){
  130. handles[i].remove();
  131. }
  132. };
  133. return handles;
  134. }
  135. return addListener(target, type, listener, dontFix, matchesTarget);
  136. };
  137. var touchEvents = /^touch/;
  138. function addListener(target, type, listener, dontFix, matchesTarget){
  139. // event delegation:
  140. var selector = type.match(/(.*):(.*)/);
  141. // if we have a selector:event, the last one is interpreted as an event, and we use event delegation
  142. if(selector){
  143. type = selector[2];
  144. selector = selector[1];
  145. // create the extension event for selectors and directly call it
  146. return on.selector(selector, type).call(matchesTarget, target, listener);
  147. }
  148. // test to see if it a touch event right now, so we don't have to do it every time it fires
  149. if(has("touch")){
  150. if(touchEvents.test(type)){
  151. // touch event, fix it
  152. listener = fixTouchListener(listener);
  153. }
  154. if(!has("event-orientationchange") && (type == "orientationchange")){
  155. //"orientationchange" not supported <= Android 2.1,
  156. //but works through "resize" on window
  157. type = "resize";
  158. target = window;
  159. listener = fixTouchListener(listener);
  160. }
  161. }
  162. if(addStopImmediate){
  163. // add stopImmediatePropagation if it doesn't exist
  164. listener = addStopImmediate(listener);
  165. }
  166. // normal path, the target is |this|
  167. if(target.addEventListener){
  168. // the target has addEventListener, which should be used if available (might or might not be a node, non-nodes can implement this method as well)
  169. // check for capture conversions
  170. var capture = type in captures,
  171. adjustedType = capture ? captures[type] : type;
  172. target.addEventListener(adjustedType, listener, capture);
  173. // create and return the signal
  174. return {
  175. remove: function(){
  176. target.removeEventListener(adjustedType, listener, capture);
  177. }
  178. };
  179. }
  180. type = "on" + type;
  181. if(fixAttach && target.attachEvent){
  182. return fixAttach(target, type, listener);
  183. }
  184. throw new Error("Target must be an event emitter");
  185. }
  186. on.matches = function(node, selector, context, children, matchesTarget) {
  187. // summary:
  188. // Check if a node match the current selector within the constraint of a context
  189. // node: DOMNode
  190. // The node that originate the event
  191. // selector: String
  192. // The selector to check against
  193. // context: DOMNode
  194. // The context to search in.
  195. // children: Boolean
  196. // Indicates if children elements of the selector should be allowed. This defaults to
  197. // true
  198. // matchesTarget: Object|dojo/query?
  199. // An object with a property "matches" as a function. Default is dojo/query.
  200. // Matching DOMNodes will be done against this function
  201. // The function must return a Boolean.
  202. // It will have 3 arguments: "node", "selector" and "context"
  203. // True is expected if "node" is matching the current "selector" in the passed "context"
  204. // returns: DOMNode?
  205. // The matching node, if any. Else you get false
  206. // see if we have a valid matchesTarget or default to dojo/query
  207. matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query;
  208. children = children !== false;
  209. // there is a selector, so make sure it matches
  210. if(node.nodeType != 1){
  211. // text node will fail in native match selector
  212. node = node.parentNode;
  213. }
  214. while(!matchesTarget.matches(node, selector, context)){
  215. if(node == context || children === false || !(node = node.parentNode) || node.nodeType != 1){ // intentional assignment
  216. return false;
  217. }
  218. }
  219. return node;
  220. }
  221. on.selector = function(selector, eventType, children){
  222. // summary:
  223. // Creates a new extension event with event delegation. This is based on
  224. // the provided event type (can be extension event) that
  225. // only calls the listener when the CSS selector matches the target of the event.
  226. //
  227. // The application must require() an appropriate level of dojo/query to handle the selector.
  228. // selector:
  229. // The CSS selector to use for filter events and determine the |this| of the event listener.
  230. // eventType:
  231. // The event to listen for
  232. // children:
  233. // Indicates if children elements of the selector should be allowed. This defaults to
  234. // true
  235. // example:
  236. // | require(["dojo/on", "dojo/mouse", "dojo/query!css2"], function(on, mouse){
  237. // | on(node, on.selector(".my-class", mouse.enter), handlerForMyHover);
  238. return function(target, listener){
  239. // if the selector is function, use it to select the node, otherwise use the matches method
  240. var matchesTarget = typeof selector == "function" ? {matches: selector} : this,
  241. bubble = eventType.bubble;
  242. function select(eventTarget){
  243. return on.matches(eventTarget, selector, target, children, matchesTarget);
  244. }
  245. if(bubble){
  246. // the event type doesn't naturally bubble, but has a bubbling form, use that, and give it the selector so it can perform the select itself
  247. return on(target, bubble(select), listener);
  248. }
  249. // standard event delegation
  250. return on(target, eventType, function(event){
  251. // call select to see if we match
  252. var eventTarget = select(event.target);
  253. // if it matches we call the listener
  254. if (eventTarget) {
  255. return listener.call(eventTarget, event);
  256. }
  257. });
  258. };
  259. };
  260. function syntheticPreventDefault(){
  261. this.cancelable = false;
  262. this.defaultPrevented = true;
  263. }
  264. function syntheticStopPropagation(){
  265. this.bubbles = false;
  266. }
  267. var slice = [].slice,
  268. syntheticDispatch = on.emit = function(target, type, event){
  269. // summary:
  270. // Fires an event on the target object.
  271. // target:
  272. // The target object to fire the event on. This can be a DOM element or a plain
  273. // JS object. If the target is a DOM element, native event emitting mechanisms
  274. // are used when possible.
  275. // type:
  276. // The event type name. You can emulate standard native events like "click" and
  277. // "mouseover" or create custom events like "open" or "finish".
  278. // event:
  279. // An object that provides the properties for the event. See https://developer.mozilla.org/en/DOM/event.initEvent
  280. // for some of the properties. These properties are copied to the event object.
  281. // Of particular importance are the cancelable and bubbles properties. The
  282. // cancelable property indicates whether or not the event has a default action
  283. // that can be cancelled. The event is cancelled by calling preventDefault() on
  284. // the event object. The bubbles property indicates whether or not the
  285. // event will bubble up the DOM tree. If bubbles is true, the event will be called
  286. // on the target and then each parent successively until the top of the tree
  287. // is reached or stopPropagation() is called. Both bubbles and cancelable
  288. // default to false.
  289. // returns:
  290. // If the event is cancelable and the event is not cancelled,
  291. // emit will return true. If the event is cancelable and the event is cancelled,
  292. // emit will return false.
  293. // details:
  294. // Note that this is designed to emit events for listeners registered through
  295. // dojo/on. It should actually work with any event listener except those
  296. // added through IE's attachEvent (IE8 and below's non-W3C event emitting
  297. // doesn't support custom event types). It should work with all events registered
  298. // through dojo/on. Also note that the emit method does do any default
  299. // action, it only returns a value to indicate if the default action should take
  300. // place. For example, emitting a keypress event would not cause a character
  301. // to appear in a textbox.
  302. // example:
  303. // To fire our own click event
  304. // | require(["dojo/on", "dojo/dom"
  305. // | ], function(on, dom){
  306. // | on.emit(dom.byId("button"), "click", {
  307. // | cancelable: true,
  308. // | bubbles: true,
  309. // | screenX: 33,
  310. // | screenY: 44
  311. // | });
  312. // We can also fire our own custom events:
  313. // | on.emit(dom.byId("slider"), "slide", {
  314. // | cancelable: true,
  315. // | bubbles: true,
  316. // | direction: "left-to-right"
  317. // | });
  318. // | });
  319. var args = slice.call(arguments, 2);
  320. var method = "on" + type;
  321. if("parentNode" in target){
  322. // node (or node-like), create event controller methods
  323. var newEvent = args[0] = {};
  324. for(var i in event){
  325. newEvent[i] = event[i];
  326. }
  327. newEvent.preventDefault = syntheticPreventDefault;
  328. newEvent.stopPropagation = syntheticStopPropagation;
  329. newEvent.target = target;
  330. newEvent.type = type;
  331. event = newEvent;
  332. }
  333. do{
  334. // call any node which has a handler (note that ideally we would try/catch to simulate normal event propagation but that causes too much pain for debugging)
  335. target[method] && target[method].apply(target, args);
  336. // and then continue up the parent node chain if it is still bubbling (if started as bubbles and stopPropagation hasn't been called)
  337. }while(event && event.bubbles && (target = target.parentNode));
  338. return event && event.cancelable && event; // if it is still true (was cancelable and was cancelled), return the event to indicate default action should happen
  339. };
  340. var captures = has("event-focusin") ? {} : {focusin: "focus", focusout: "blur"};
  341. if(!has("event-stopimmediatepropagation")){
  342. var stopImmediatePropagation =function(){
  343. this.immediatelyStopped = true;
  344. this.modified = true; // mark it as modified so the event will be cached in IE
  345. };
  346. var addStopImmediate = function(listener){
  347. return function(event){
  348. if(!event.immediatelyStopped){// check to make sure it hasn't been stopped immediately
  349. event.stopImmediatePropagation = stopImmediatePropagation;
  350. return listener.apply(this, arguments);
  351. }
  352. };
  353. }
  354. }
  355. if(has("dom-addeventlistener")){
  356. // emitter that works with native event handling
  357. on.emit = function(target, type, event){
  358. if(target.dispatchEvent && document.createEvent){
  359. // use the native event emitting mechanism if it is available on the target object
  360. // create a generic event
  361. // we could create branch into the different types of event constructors, but
  362. // that would be a lot of extra code, with little benefit that I can see, seems
  363. // best to use the generic constructor and copy properties over, making it
  364. // easy to have events look like the ones created with specific initializers
  365. var ownerDocument = target.ownerDocument || document;
  366. var nativeEvent = ownerDocument.createEvent("HTMLEvents");
  367. nativeEvent.initEvent(type, !!event.bubbles, !!event.cancelable);
  368. // and copy all our properties over
  369. for(var i in event){
  370. if(!(i in nativeEvent)){
  371. nativeEvent[i] = event[i];
  372. }
  373. }
  374. return target.dispatchEvent(nativeEvent) && nativeEvent;
  375. }
  376. return syntheticDispatch.apply(on, arguments); // emit for a non-node
  377. };
  378. }else{
  379. // no addEventListener, basically old IE event normalization
  380. on._fixEvent = function(evt, sender){
  381. // summary:
  382. // normalizes properties on the event object including event
  383. // bubbling methods, keystroke normalization, and x/y positions
  384. // evt:
  385. // native event object
  386. // sender:
  387. // node to treat as "currentTarget"
  388. if(!evt){
  389. var w = sender && (sender.ownerDocument || sender.document || sender).parentWindow || window;
  390. evt = w.event;
  391. }
  392. if(!evt){return evt;}
  393. try{
  394. if(lastEvent && evt.type == lastEvent.type && evt.srcElement == lastEvent.target){
  395. // should be same event, reuse event object (so it can be augmented);
  396. // accessing evt.srcElement rather than evt.target since evt.target not set on IE until fixup below
  397. evt = lastEvent;
  398. }
  399. }catch(e){
  400. // will occur on IE on lastEvent.type reference if lastEvent points to a previous event that already
  401. // finished bubbling, but the setTimeout() to clear lastEvent hasn't fired yet
  402. }
  403. if(!evt.target){ // check to see if it has been fixed yet
  404. evt.target = evt.srcElement;
  405. evt.currentTarget = (sender || evt.srcElement);
  406. if(evt.type == "mouseover"){
  407. evt.relatedTarget = evt.fromElement;
  408. }
  409. if(evt.type == "mouseout"){
  410. evt.relatedTarget = evt.toElement;
  411. }
  412. if(!evt.stopPropagation){
  413. evt.stopPropagation = stopPropagation;
  414. evt.preventDefault = preventDefault;
  415. }
  416. switch(evt.type){
  417. case "keypress":
  418. var c = ("charCode" in evt ? evt.charCode : evt.keyCode);
  419. if (c==10){
  420. // CTRL-ENTER is CTRL-ASCII(10) on IE, but CTRL-ENTER on Mozilla
  421. c=0;
  422. evt.keyCode = 13;
  423. }else if(c==13||c==27){
  424. c=0; // Mozilla considers ENTER and ESC non-printable
  425. }else if(c==3){
  426. c=99; // Mozilla maps CTRL-BREAK to CTRL-c
  427. }
  428. // Mozilla sets keyCode to 0 when there is a charCode
  429. // but that stops the event on IE.
  430. evt.charCode = c;
  431. _setKeyChar(evt);
  432. break;
  433. }
  434. }
  435. return evt;
  436. };
  437. var lastEvent, IESignal = function(handle){
  438. this.handle = handle;
  439. };
  440. IESignal.prototype.remove = function(){
  441. delete _dojoIEListeners_[this.handle];
  442. };
  443. var fixListener = function(listener){
  444. // this is a minimal function for closing on the previous listener with as few as variables as possible
  445. return function(evt){
  446. evt = on._fixEvent(evt, this);
  447. var result = listener.call(this, evt);
  448. if(evt.modified){
  449. // cache the last event and reuse it if we can
  450. if(!lastEvent){
  451. setTimeout(function(){
  452. lastEvent = null;
  453. });
  454. }
  455. lastEvent = evt;
  456. }
  457. return result;
  458. };
  459. };
  460. var fixAttach = function(target, type, listener){
  461. listener = fixListener(listener);
  462. if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top ||
  463. has("jscript") < 5.8) &&
  464. !has("config-_allow_leaks")){
  465. // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below.
  466. // Here we use global redirection to solve the memory leaks
  467. if(typeof _dojoIEListeners_ == "undefined"){
  468. _dojoIEListeners_ = [];
  469. }
  470. var emitter = target[type];
  471. if(!emitter || !emitter.listeners){
  472. var oldListener = emitter;
  473. emitter = Function('event', 'var callee = arguments.callee; for(var i = 0; i<callee.listeners.length; i++){var listener = _dojoIEListeners_[callee.listeners[i]]; if(listener){listener.call(this,event);}}');
  474. emitter.listeners = [];
  475. target[type] = emitter;
  476. emitter.global = this;
  477. if(oldListener){
  478. emitter.listeners.push(_dojoIEListeners_.push(oldListener) - 1);
  479. }
  480. }
  481. var handle;
  482. emitter.listeners.push(handle = (emitter.global._dojoIEListeners_.push(listener) - 1));
  483. return new IESignal(handle);
  484. }
  485. return aspect.after(target, type, listener, true);
  486. };
  487. var _setKeyChar = function(evt){
  488. evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : '';
  489. evt.charOrCode = evt.keyChar || evt.keyCode; // TODO: remove for 2.0
  490. };
  491. // Called in Event scope
  492. var stopPropagation = function(){
  493. this.cancelBubble = true;
  494. };
  495. var preventDefault = on._preventDefault = function(){
  496. // Setting keyCode to 0 is the only way to prevent certain keypresses (namely
  497. // ctrl-combinations that correspond to menu accelerator keys).
  498. // Otoh, it prevents upstream listeners from getting this information
  499. // Try to split the difference here by clobbering keyCode only for ctrl
  500. // combinations. If you still need to access the key upstream, bubbledKeyCode is
  501. // provided as a workaround.
  502. this.bubbledKeyCode = this.keyCode;
  503. if(this.ctrlKey){
  504. try{
  505. // squelch errors when keyCode is read-only
  506. // (e.g. if keyCode is ctrl or shift)
  507. this.keyCode = 0;
  508. }catch(e){
  509. }
  510. }
  511. this.defaultPrevented = true;
  512. this.returnValue = false;
  513. this.modified = true; // mark it as modified (for defaultPrevented flag) so the event will be cached in IE
  514. };
  515. }
  516. if(has("touch")){
  517. var EventDelegate = function(){};
  518. var windowOrientation = window.orientation;
  519. var fixTouchListener = function(listener){
  520. return function(originalEvent){
  521. //Event normalization(for ontouchxxx and resize):
  522. //1.incorrect e.pageX|pageY in iOS
  523. //2.there are no "e.rotation", "e.scale" and "onorientationchange" in Android
  524. //3.More TBD e.g. force | screenX | screenX | clientX | clientY | radiusX | radiusY
  525. // see if it has already been corrected
  526. var event = originalEvent.corrected;
  527. if(!event){
  528. var type = originalEvent.type;
  529. try{
  530. delete originalEvent.type; // on some JS engines (android), deleting properties makes them mutable
  531. }catch(e){}
  532. if(originalEvent.type){
  533. // Deleting the property of the original event did not work (this is the case of
  534. // browsers such as older Safari iOS), hence fallback:
  535. if(has("touch-can-modify-event-delegate")){
  536. // If deleting properties of delegated event works, use event delegation:
  537. EventDelegate.prototype = originalEvent;
  538. event = new EventDelegate;
  539. }else{
  540. // Otherwise last fallback: other browsers, such as mobile Firefox, do not like
  541. // delegated properties, so we have to copy
  542. event = {};
  543. for(var name in originalEvent){
  544. event[name] = originalEvent[name];
  545. }
  546. }
  547. // have to delegate methods to make them work
  548. event.preventDefault = function(){
  549. originalEvent.preventDefault();
  550. };
  551. event.stopPropagation = function(){
  552. originalEvent.stopPropagation();
  553. };
  554. }else{
  555. // deletion worked, use property as is
  556. event = originalEvent;
  557. event.type = type;
  558. }
  559. originalEvent.corrected = event;
  560. if(type == 'resize'){
  561. if(windowOrientation == window.orientation){
  562. return null;//double tap causes an unexpected 'resize' in Android
  563. }
  564. windowOrientation = window.orientation;
  565. event.type = "orientationchange";
  566. return listener.call(this, event);
  567. }
  568. // We use the original event and augment, rather than doing an expensive mixin operation
  569. if(!("rotation" in event)){ // test to see if it has rotation
  570. event.rotation = 0;
  571. event.scale = 1;
  572. }
  573. //use event.changedTouches[0].pageX|pageY|screenX|screenY|clientX|clientY|target
  574. var firstChangeTouch = event.changedTouches[0];
  575. for(var i in firstChangeTouch){ // use for-in, we don't need to have dependency on dojo/_base/lang here
  576. delete event[i]; // delete it first to make it mutable
  577. event[i] = firstChangeTouch[i];
  578. }
  579. }
  580. return listener.call(this, event);
  581. };
  582. };
  583. }
  584. return on;
  585. });