├── AUTHORS ├── .gitignore ├── .travis.yml ├── tests ├── tests.js ├── tests.html └── phantom.js ├── README.md ├── package.json ├── on.js ├── emit.js ├── emitOldIE.js ├── _base ├── event.js └── connect.js ├── mouse.js ├── node.js ├── nodeOldIE.js ├── LICENSE ├── touch.js └── on.orig.js /AUTHORS: -------------------------------------------------------------------------------- 1 | Eugene Lazutkin (http://lazutkin.com/) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.iml 3 | .idea 4 | *.sublime-* 5 | report/* 6 | coverage/* 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | script: phantomjs tests/phantom.js 7 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) 2 | (["module", "heya-unit"], 3 | function(module, unit){ 4 | "use strict"; 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | ]); 10 | 11 | unit.run(); 12 | }); 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DOM-events [![Build Status](https://travis-ci.org/heya/dom-events.png?branch=master)](https://travis-ci.org/heya/dom-events) 2 | 3 | DOM-events: event streams for browsers. 4 | 5 | ## How to install 6 | 7 | If you plan to use it in your [node.js](http://nodejs.org) project install it 8 | like this: 9 | 10 | ``` 11 | npm install heya-dom-events 12 | ``` 13 | 14 | For your browser-based projects I suggest to use [volo.js](http://volojs.org): 15 | 16 | ``` 17 | volo install heya/dom-events heya-dom-events 18 | ``` 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heya-dom-events", 3 | "version": "0.1.0", 4 | "description": "DOM-events: event streams for browsers.", 5 | "main": "main.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "dependencies": { 10 | "heya-has": ">=0.1", 11 | "heya-dom": ">=0.1", 12 | "heya-events": ">=0.1" 13 | }, 14 | "devDependencies": { 15 | "heya-unit": ">=0.1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/heya/dom-events.git" 20 | }, 21 | "keywords": [ 22 | "DOM", 23 | "events", 24 | "event stream" 25 | ], 26 | "author": "Eugene Lazutkin (http://lazutkin.com/)", 27 | "license": "BSD" 28 | } 29 | -------------------------------------------------------------------------------- /on.js: -------------------------------------------------------------------------------- 1 | define(["./node", "heya-events/EventSource"], function(NodeEvents){ 2 | "use strict"; 3 | 4 | return function on(node, type, filter){ 5 | if(typeof type == "function"){ 6 | return type(node); 7 | } 8 | if(type instanceof Array){ 9 | var merger = new NodeEvents(null, null, filter), 10 | sources = type.map(function(type){ 11 | var source = on(node, type); 12 | source.on(function(evt){ 13 | merger.dispatch(evt); 14 | return evt; 15 | }); 16 | return source; 17 | }); 18 | merger._removals.push(function(){ 19 | sources.forEach(function(source){ source.remove(); }); 20 | sources = null; 21 | }); 22 | return merger; 23 | } 24 | return new NodeEvents(node, type, filter); 25 | }; 26 | }); 27 | -------------------------------------------------------------------------------- /emit.js: -------------------------------------------------------------------------------- 1 | define(["heya-has/sniff", "heya-dom/dom"], function(has, dom){ 2 | "use strict"; 3 | 4 | //TODO: the algorithm below is depricated, 5 | // MDN suggests to use event constructors 6 | 7 | return function emit(target, evt){ 8 | // use the native event emitting mechanism if it is available on the target object 9 | // create a generic event 10 | // we could create branch into the different types of event constructors, but 11 | // that would be a lot of extra code, with little benefit that I can see, seems 12 | // best to use the generic constructor and copy properties over, making it 13 | // easy to have events look like the ones created with specific initializers 14 | var nativeEvent = target.ownerDocument.createEvent("HTMLEvents"); 15 | nativeEvent.initEvent(evt.type, !!evt.bubbles, !!evt.cancelable); 16 | // and copy all our properties over 17 | for(var name in evt){ 18 | if(!(name in nativeEvent)){ 19 | nativeEvent[name] = evt[name]; 20 | } 21 | } 22 | return target.dispatchEvent(nativeEvent) && nativeEvent; 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heya-dom-events test runner 5 | 6 | 7 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/phantom.js: -------------------------------------------------------------------------------- 1 | phantom.onError = function(msg, trace){ 2 | var msgStack = ["PHANTOM ERROR: " + msg]; 3 | if(trace){ 4 | msgStack.push("TRACE:"); 5 | trace.forEach(function(t){ 6 | msgStack.push(" -> " + (t.file || t.sourceURL) + ": " + t.line + 7 | (t.function ? " (in function " + t.function + ")" : "")); 8 | }); 9 | } 10 | console.error(msgStack.join('\n')); 11 | phantom.exit(1); 12 | }; 13 | 14 | var page = require("webpage").create(); 15 | 16 | page.onError = function(msg){ 17 | console.error("ERROR: " + msg); 18 | phantom.exit(1); 19 | }; 20 | 21 | page.onAlert = function(msg){ 22 | console.log("ALERT: " + msg); 23 | }; 24 | page.onConsoleMessage = function(msg){ 25 | console.log(msg); 26 | }; 27 | page.onCallback = function(msg){ 28 | switch(msg){ 29 | case "success": 30 | phantom.exit(0); 31 | break; 32 | case "failure": 33 | phantom.exit(1); 34 | break; 35 | } 36 | } 37 | 38 | var scriptPath = require("system").args[0], 39 | path = require("fs").absolute( 40 | (scriptPath.length && scriptPath.charAt(0) == "/" ? "" : "./") + scriptPath).split("/"); 41 | 42 | path.pop(); 43 | path.push("tests.html"); 44 | 45 | page.open(path.join("/"), function(status){ 46 | if(status !== "success"){ 47 | console.error("ERROR: Can't load a web page."); 48 | phantom.exit(1); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /emitOldIE.js: -------------------------------------------------------------------------------- 1 | define(["heya-has/sniff", "heya-dom/dom"], function(has, dom){ 2 | 3 | // It is probably better to use fireEvent(), 4 | // but given that it is non-standard, and supported 5 | // only by IE < 9, we'll let it slide. 6 | 7 | return function emit(target, evt){ 8 | var args = slice.call(arguments, 2); 9 | var method = "on" + evt.type; 10 | if("parentNode" in target){ 11 | // node (or node-like), create event controller methods 12 | var newEvent = args[0] = {}; 13 | for(var i in evt){ 14 | newEvent[i] = evt[i]; 15 | } 16 | newEvent.preventDefault = syntheticPreventDefault; 17 | newEvent.stopPropagation = syntheticStopPropagation; 18 | newEvent.target = target; 19 | evt = newEvent; 20 | } 21 | do{ 22 | // 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) 23 | target[method] && target[method].apply(target, args); 24 | // and then continue up the parent node chain if it is still bubbling (if started as bubbles and stopPropagation hasn't been called) 25 | }while(evt && evt.bubbles && (target = target.parentNode)); 26 | return evt && evt.cancelable && evt; // if it is still true (was cancelable and was cancelled), return the event to indicate default action should happen 27 | }; 28 | 29 | function syntheticPreventDefault(){ 30 | this.cancelable = false; 31 | this.defaultPrevented = true; 32 | } 33 | 34 | function syntheticStopPropagation(){ 35 | this.bubbles = false; 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /_base/event.js: -------------------------------------------------------------------------------- 1 | define(["./kernel", "../on", "../has", "../dom-geometry"], function(dojo, on, has, dom){ 2 | // module: 3 | // dojo/_base/event 4 | 5 | if(on._fixEvent){ 6 | var fixEvent = on._fixEvent; 7 | on._fixEvent = function(evt, se){ 8 | // add some additional normalization for back-compat, this isn't in on.js because it is somewhat more expensive 9 | evt = fixEvent(evt, se); 10 | if(evt){ 11 | dom.normalizeEvent(evt); 12 | } 13 | return evt; 14 | }; 15 | } 16 | 17 | var ret = { 18 | // summary: 19 | // This module defines dojo DOM event API. Usually you should use dojo/on, and evt.stopPropagation() + 20 | // evt.preventDefault(), rather than this module. 21 | 22 | fix: function(/*Event*/ evt, /*DOMNode*/ sender){ 23 | // summary: 24 | // normalizes properties on the event object including event 25 | // bubbling methods, keystroke normalization, and x/y positions 26 | // evt: Event 27 | // native event object 28 | // sender: DOMNode 29 | // node to treat as "currentTarget" 30 | if(on._fixEvent){ 31 | return on._fixEvent(evt, sender); 32 | } 33 | return evt; // Event 34 | }, 35 | 36 | stop: function(/*Event*/ evt){ 37 | // summary: 38 | // prevents propagation and clobbers the default action of the 39 | // passed event 40 | // evt: Event 41 | // The event object. If omitted, window.event is used on IE. 42 | if(has("dom-addeventlistener") || (evt && evt.preventDefault)){ 43 | evt.preventDefault(); 44 | evt.stopPropagation(); 45 | }else{ 46 | evt = evt || window.event; 47 | evt.cancelBubble = true; 48 | on._preventDefault.call(evt); 49 | } 50 | } 51 | }; 52 | 53 | if(has("extend-dojo")){ 54 | dojo.fixEvent = ret.fix; 55 | dojo.stopEvent = ret.stop; 56 | } 57 | 58 | return ret; 59 | }); 60 | -------------------------------------------------------------------------------- /mouse.js: -------------------------------------------------------------------------------- 1 | define(["heya-has/sniff", "heya-dom/dom", "./on"], function(has, dom, on){ 2 | "use strict"; 3 | 4 | // module: 5 | // dojo/mouse 6 | 7 | has.add("dom-quirks", function(global, doc){ 8 | return doc && doc.compatMode === "BackCompat"; 9 | }); 10 | 11 | has.add("events-mouseenter", function(global, doc, element){ 12 | return element && "onmouseenter" in element; 13 | }); 14 | has.add("events-mousewheel", function(global, doc){ 15 | return doc && "onmousewheel" in doc; 16 | }); 17 | 18 | var mouseButtons; 19 | if((has("dom-quirks") && has("ie")) || !has("dom-addeventlistener")){ 20 | mouseButtons = { 21 | LEFT: 1, 22 | MIDDLE: 4, 23 | RIGHT: 2, 24 | // helper functions 25 | isButton: function(e, button){ return e.button & button; }, 26 | isLeft: function(e){ return e.button & 1; }, 27 | isMiddle: function(e){ return e.button & 4; }, 28 | isRight: function(e){ return e.button & 2; } 29 | }; 30 | }else{ 31 | mouseButtons = { 32 | LEFT: 0, 33 | MIDDLE: 1, 34 | RIGHT: 2, 35 | // helper functions 36 | isButton: function(e, button){ return e.button == button; }, 37 | isLeft: function(e){ return e.button == 0; }, 38 | isMiddle: function(e){ return e.button == 1; }, 39 | isRight: function(e){ return e.button == 2; } 40 | }; 41 | } 42 | 43 | /*===== 44 | dojo.mouseButtons = { 45 | // LEFT: Number 46 | // Numeric value of the left mouse button for the platform. 47 | LEFT: 0, 48 | // MIDDLE: Number 49 | // Numeric value of the middle mouse button for the platform. 50 | MIDDLE: 1, 51 | // RIGHT: Number 52 | // Numeric value of the right mouse button for the platform. 53 | RIGHT: 2, 54 | 55 | isButton: function(e, button){ 56 | // summary: 57 | // Checks an event object for a pressed button 58 | // e: Event 59 | // Event object to examine 60 | // button: Number 61 | // The button value (example: dojo.mouseButton.LEFT) 62 | return e.button == button; // Boolean 63 | }, 64 | isLeft: function(e){ 65 | // summary: 66 | // Checks an event object for the pressed left button 67 | // e: Event 68 | // Event object to examine 69 | return e.button == 0; // Boolean 70 | }, 71 | isMiddle: function(e){ 72 | // summary: 73 | // Checks an event object for the pressed middle button 74 | // e: Event 75 | // Event object to examine 76 | return e.button == 1; // Boolean 77 | }, 78 | isRight: function(e){ 79 | // summary: 80 | // Checks an event object for the pressed right button 81 | // e: Event 82 | // Event object to examine 83 | return e.button == 2; // Boolean 84 | } 85 | }; 86 | =====*/ 87 | 88 | function eventHandler(type){ 89 | // emulation of mouseenter/leave with mouseover/out using descendant checking 90 | function handler(node){ 91 | return on(node, type, function(evt, sink){ 92 | return !dom.isDescendant(evt.relatedTarget, node) ? evt : sink.noValue; 93 | }); 94 | }; 95 | handler.bubble = function(select){ 96 | return eventHandler(type, function(evt, sink){ 97 | // using a selector, use the select function to determine 98 | // if the mouse moved inside the selector and was previously 99 | // outside the selector 100 | var target = select(evt.target), relatedTarget = evt.relatedTarget; 101 | if(target && (target !== (relatedTarget && relatedTarget.nodeType == 1 && select(relatedTarget)))){ 102 | return evt; 103 | } 104 | return sink.noValue; 105 | }); 106 | }; 107 | return handler; 108 | } 109 | var wheel; 110 | if(has("events-mousewheel")){ 111 | wheel = "mousewheel"; 112 | }else{ //firefox 113 | wheel = function(node){ 114 | return on(node, "DOMMouseScroll", function(evt){ 115 | evt.wheelDelta = -evt.detail; 116 | return evt; 117 | }); 118 | }; 119 | } 120 | return { 121 | // summary: 122 | // This module provide mouse event handling utility functions and exports 123 | // mouseenter and mouseleave event emulation. 124 | // example: 125 | // To use these events, you register a mouseenter like this: 126 | // | define(["dojo/on", dojo/mouse"], function(on, mouse){ 127 | // | on(targetNode, mouse.enter, function(event){ 128 | // | dojo.addClass(targetNode, "highlighted"); 129 | // | }); 130 | // | on(targetNode, mouse.leave, function(event){ 131 | // | dojo.removeClass(targetNode, "highlighted"); 132 | // | }); 133 | 134 | _eventHandler: eventHandler, // for dojo/touch 135 | 136 | // enter: Synthetic Event 137 | // This is an extension event for the mouseenter that IE provides, emulating the 138 | // behavior on other browsers. 139 | enter: eventHandler("mouseover"), 140 | 141 | // leave: Synthetic Event 142 | // This is an extension event for the mouseleave that IE provides, emulating the 143 | // behavior on other browsers. 144 | leave: eventHandler("mouseout"), 145 | 146 | // wheel: Normalized Mouse Wheel Event 147 | // This is an extension event for the mousewheel that non-Mozilla browsers provide, 148 | // emulating the behavior on Mozilla based browsers. 149 | wheel: wheel, 150 | 151 | isLeft: mouseButtons.isLeft, 152 | /*===== 153 | isLeft: function(){ 154 | // summary: 155 | // Test an event object (from a mousedown event) to see if the left button was pressed. 156 | }, 157 | =====*/ 158 | 159 | isMiddle: mouseButtons.isMiddle, 160 | /*===== 161 | isMiddle: function(){ 162 | // summary: 163 | // Test an event object (from a mousedown event) to see if the middle button was pressed. 164 | }, 165 | =====*/ 166 | 167 | isRight: mouseButtons.isRight 168 | /*===== 169 | , isRight: function(){ 170 | // summary: 171 | // Test an event object (from a mousedown event) to see if the right button was pressed. 172 | } 173 | =====*/ 174 | }; 175 | }); 176 | -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | define(["heya-has/sniff", "heya-dom/dom", "heya-events/EventSource"], 2 | function(has, dom, EventSource){ 3 | "use strict"; 4 | 5 | has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this? 6 | has.add("event-stopimmediatepropagation", function(global){ 7 | return global.Event && global.Event.prototype && !!global.Event.prototype.stopImmediatePropagation; 8 | }); 9 | has.add("event-focusin", function(global, doc, element){ 10 | // All browsers except firefox support focusin, but too hard to feature test webkit since element.onfocusin 11 | // is undefined. Just return true for IE and use fallback path for other browsers. 12 | return !!element.attachEvent; 13 | }); 14 | 15 | // assumption: all modern browsers support stopImmediatePropagation() per MDN 16 | 17 | var touchEvents = /^touch/, captures = {focusin: "focus", focusout: "blur"}; 18 | 19 | function NodeEvents(source, type, filter){ 20 | EventSource.call(this); 21 | this.source = dom.byId(source); 22 | this._removals = []; 23 | 24 | if(this.source && type){ 25 | var self = this; 26 | if(typeof type == "string"){ 27 | type.replace(/\b\w+\b/g, function(name){ 28 | self.attach(name); 29 | return ""; 30 | }); 31 | }else if(type instanceof Array){ 32 | type.forEach(function(name){ 33 | self.attach(name); 34 | }); 35 | } 36 | } 37 | 38 | if(filter){ 39 | this.micro.callback = EventSource.makeMultiplexer(this, filter); 40 | } 41 | } 42 | NodeEvents.prototype = Object.create(EventSource.prototype); 43 | 44 | NodeEvents.prototype.destroy = 45 | NodeEvents.prototype.remove = 46 | NodeEvents.prototype.release = function release(){ 47 | this.remove(); 48 | EventSource.prototype.release.call(this); 49 | }; 50 | 51 | NodeEvents.prototype.attach = function(type){ 52 | if(typeof type == "function"){ 53 | return type(this); 54 | } 55 | 56 | var source = this.source, cb = listener, capture = false; 57 | // test to see if it a touch event right now, so we don't have to do it every time it fires 58 | if(has("touch")){ 59 | if(touchEvents.test(type)){ 60 | // touch event, fix it 61 | cb = _touchListener; 62 | }else if(!has("event-orientationchange") && (type == "orientationchange")){ 63 | //"orientationchange" not supported <= Android 2.1, 64 | //but works through "resize" on window 65 | type = "resize"; 66 | source = window; 67 | cb = touchListener; 68 | } 69 | } 70 | // the source has addEventListener, which should be used if available (might or might not be a node, non-nodes can implement this method as well) 71 | // check for capture conversions 72 | if(!has("event-focusin") && captures[type]){ 73 | type = captures[type]; 74 | capture = true; 75 | } 76 | cb = cb.bind(this); 77 | source.addEventListener(type, cb, capture); 78 | this._removals.push(function(){ 79 | source.removeEventListener(type, cb, capture); 80 | }); 81 | }; 82 | 83 | NodeEvents.prototype.dispatch = function(evt){ 84 | this.micro.send(new EventSource.Value(evt)); 85 | }; 86 | 87 | NodeEvents.prototype.remove = function(){ 88 | this._removals.forEach(function(f){ f(); }); 89 | this._removals = []; 90 | }; 91 | 92 | // utilities 93 | 94 | function listener(evt){ 95 | this.dispatch(evt); 96 | } 97 | 98 | var windowOrientation = window.orientation; 99 | 100 | function PseudoEvent(){} 101 | 102 | function touchListener(evt){ 103 | //Event normalization(for ontouchxxx and resize): 104 | //1.incorrect e.pageX|pageY in iOS 105 | //2.there are no "e.rotation", "e.scale" and "onorientationchange" in Android 106 | //3.More TBD e.g. force | screenX | screenX | clientX | clientY | radiusX | radiusY 107 | 108 | // see if it has already been corrected 109 | if(evt.corrected){ 110 | evt = evt.corrected; 111 | }else{ 112 | var type = evt.type, newEvt; 113 | try{ 114 | delete evt.type; // on some JS engines (android), deleting properties make them mutable 115 | }catch(e){} 116 | if(evt.type){ 117 | // deleting properties doesn't work (older iOS), have to use delegation 118 | if(has("mozilla")){ 119 | // Firefox doesn't like delegated properties, so we have to copy 120 | newEvt = {}; 121 | for(var name in evt){ 122 | newEvt[name] = evt[name]; 123 | } 124 | }else{ 125 | // old iOS branch 126 | PseudoEvent.prototype = evt; 127 | newEvt = new PseudoEvent; 128 | } 129 | // have to delegate methods to make them work 130 | newEvt.preventDefault = function(){ 131 | evt.preventDefault(); 132 | }; 133 | newEvt.stopPropagation = function(){ 134 | evt.stopPropagation(); 135 | }; 136 | }else{ 137 | // deletion worked, use property as is 138 | newEvt = evt; 139 | newEvt.type = type; 140 | } 141 | evt.corrected = newEvt; 142 | if(type == "resize"){ 143 | if(windowOrientation === window.orientation){ 144 | return; //double tap causes an unexpected 'resize' in Android 145 | } 146 | windowOrientation = window.orientation; 147 | newEvt.type = "orientationchange"; 148 | }else{ 149 | // We use the original event and augment, rather than doing an expensive mixin operation 150 | if(!("rotation" in newEvt)){ // test to see if it has rotation 151 | newEvt.rotation = 0; 152 | newEvt.scale = 1; 153 | } 154 | //use newEvt.changedTouches[0].pageX|pageY|screenX|screenY|clientX|clientY|target 155 | var firstChangeTouch = newEvt.changedTouches[0]; 156 | for(var i in firstChangeTouch){ // use for-in, we don't need to have dependency on dojo/_base/lang here 157 | delete newEvt[i]; // delete it first to make it mutable 158 | newEvt[i] = firstChangeTouch[i]; 159 | } 160 | } 161 | evt = newEvt; 162 | } 163 | 164 | this.dispatch(evt); 165 | } 166 | 167 | return NodeEvents; 168 | }); 169 | -------------------------------------------------------------------------------- /nodeOldIE.js: -------------------------------------------------------------------------------- 1 | define(["heya-has/sniff", "heya-dom/dom", "heya-events/EventSource"], 2 | function(has, dom, EventSource){ 3 | 4 | var major = window.ScriptEngineMajorVersion; 5 | has.add("jscript", major && (major() + ScriptEngineMinorVersion() / 10)); 6 | 7 | has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this? 8 | has.add("event-stopimmediatepropagation", function(global){ 9 | return global.Event && global.Event.prototype && !!global.Event.prototype.stopImmediatePropagation; 10 | }); 11 | has.add("event-focusin", function(global, doc, element){ 12 | // All browsers except firefox support focusin, but too hard to feature test webkit since element.onfocusin 13 | // is undefined. Just return true for IE and use fallback path for other browsers. 14 | return !!element.attachEvent; 15 | }); 16 | 17 | function NodeEvents(source, type, filter){ 18 | EventSource.call(this); 19 | this.source = dom.byId(source); 20 | this._removals = []; 21 | 22 | if(this.source && type){ 23 | var self = this; 24 | if(typeof type == "string"){ 25 | type.replace(/\b\w+\b/g, function(name){ 26 | self.attach(name); 27 | return ""; 28 | }); 29 | }else if(type instanceof Array){ 30 | type.forEach(function(name){ 31 | self.attach(name); 32 | }); 33 | } 34 | } 35 | 36 | if(filter){ 37 | this.micro.callback = EventSource.makeMultiplexer(this, filter); 38 | } 39 | } 40 | NodeEvents.prototype = Object.create(EventSource.prototype); 41 | 42 | NodeEvents.prototype.destroy = 43 | NodeEvents.prototype.remove = 44 | NodeEvents.prototype.release = function release(){ 45 | this.remove(); 46 | EventSource.prototype.release.call(this); 47 | }; 48 | 49 | NodeEvents.prototype.attach = function(type){ 50 | if(typeof type == "function"){ 51 | return type(this); 52 | } 53 | 54 | var source = this.source, capture = false; 55 | // touch events are removed because old IE do not support them 56 | // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below. 57 | // Here we use global redirection to solve the memory leaks 58 | if(typeof _dojoIEListeners_ == "undefined"){ 59 | _dojoIEListeners_ = []; 60 | } 61 | var emitter = source[type]; 62 | if(!emitter || !emitter.listeners){ 63 | var oldListener = emitter; 64 | 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); } }"); 65 | emitter.listeners = []; 66 | source[type] = emitter; 67 | emitter._dojoIEListeners_ = _dojoIEListeners_; 68 | if(oldListener){ 69 | emitter.listeners.push(_dojoIEListeners_.push(oldListener) - 1); 70 | } 71 | } 72 | var handle; 73 | emitter.listeners.push(handle = (emitter._dojoIEListeners_.push(listener) - 1)); 74 | this._removals.push(function(){ 75 | delete _dojoIEListeners_[handle]; 76 | }); 77 | }; 78 | 79 | NodeEvents.prototype.dispatch = function(evt){ 80 | this.micro.send(new EventSource.Value(evt)); 81 | }; 82 | 83 | NodeEvents.prototype.remove = function(){ 84 | this._removals.forEach(function(f){ f(); }); 85 | this._removals = []; 86 | }; 87 | 88 | // utilities 89 | 90 | var lastEvent; //TODO: why do we even need it? 91 | 92 | function listener(evt){ 93 | if(!evt){ 94 | evt = (window && (window.ownerDocument || window.document || 95 | window).parentWindow || window).event; 96 | } 97 | if(evt){ 98 | if(evt.immediatelyStopped){ 99 | return; 100 | } 101 | evt.stopImmediatePropagation = stopImmediatePropagation; 102 | try{ 103 | if(lastEvent && evt.type === lastEvent.type && evt.srcElement === lastEvent.target){ 104 | // should be same event, reuse event object (so it can be augmented); 105 | // accessing evt.srcElement rather than evt.target since evt.target not set on IE until fixup below 106 | evt = lastEvent; 107 | } 108 | }catch(e){ 109 | // will occur on IE on lastEvent.type reference if lastEvent points to a previous event that already 110 | // finished bubbling, but the setTimeout() to clear lastEvent hasn't fired yet 111 | } 112 | if(!evt.target){ // check to see if it has been fixed yet 113 | evt.target = evt.srcElement; 114 | evt.currentTarget = (window || evt.srcElement); 115 | if(evt.type == "mouseover"){ 116 | evt.relatedTarget = evt.fromElement; 117 | } 118 | if(evt.type == "mouseout"){ 119 | evt.relatedTarget = evt.toElement; 120 | } 121 | if(!evt.stopPropagation){ 122 | evt.stopPropagation = stopPropagation; 123 | evt.preventDefault = preventDefault; 124 | } 125 | if(evt.type === "keypress"){ 126 | var c = "charCode" in evt ? evt.charCode : evt.keyCode; 127 | switch(c){ 128 | case 10: 129 | // CTRL-ENTER is CTRL-ASCII(10) on IE, 130 | // but CTRL-ENTER on Mozilla 131 | c = 0; 132 | evt.keyCode = 13; 133 | break; 134 | case 13: 135 | case 27: 136 | // Mozilla considers ENTER and ESC non-printable 137 | c = 0; 138 | break; 139 | case 3: 140 | // Mozilla maps CTRL-BREAK to CTRL-c 141 | c = 99; 142 | break; 143 | } 144 | // Mozilla sets keyCode to 0 when there is a charCode 145 | // but that stops the event on IE. 146 | evt.charCode = c; 147 | setKeyChar(evt); 148 | } 149 | } 150 | } 151 | this.dispatch(evt); 152 | // a hack to capture the last event 153 | if(evt.modified){ 154 | // cache the last event and reuse it if we can 155 | if(!lastEvent){ 156 | setTimeout(function(){ lastEvent = null; }, 0); // ha-ha 157 | } 158 | lastEvent = evt; 159 | } 160 | } 161 | 162 | function setKeyChar(evt){ 163 | evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : ""; 164 | evt.charOrCode = evt.keyChar || evt.keyCode; 165 | } 166 | 167 | function stopPropagation(){ 168 | this.cancelBubble = true; 169 | } 170 | 171 | function stopImmediatePropagation(){ 172 | this.immediatelyStopped = true; 173 | this.modified = true; // mark it as modified so the event will be cached in IE 174 | } 175 | 176 | function preventDefault(){ 177 | // Setting keyCode to 0 is the only way to prevent certain keypresses (namely 178 | // ctrl-combinations that correspond to menu accelerator keys). 179 | // Otoh, it prevents upstream listeners from getting this information 180 | // Try to split the difference here by clobbering keyCode only for ctrl 181 | // combinations. If you still need to access the key upstream, bubbledKeyCode is 182 | // provided as a workaround. 183 | this.bubbledKeyCode = this.keyCode; 184 | if(this.ctrlKey){ 185 | try{ 186 | // squelch errors when keyCode is read-only 187 | // (e.g. if keyCode is ctrl or shift) 188 | this.keyCode = 0; 189 | }catch(e){} 190 | } 191 | this.defaultPrevented = true; 192 | this.returnValue = false; 193 | this.modified = true; // mark it as modified (for defaultPrevented flag) so the event will be cached in IE 194 | }; 195 | 196 | return NodeEvents; 197 | }); 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This library is available under *either* the terms of the modified BSD license 2 | *or* the Academic Free License version 2.1. As a recipient of this work, you 3 | may choose which license to receive this code under. No external contributions 4 | are allowed under licenses which are fundamentally incompatible with the AFL 5 | or BSD licenses that this library is distributed under. 6 | 7 | The text of the AFL and BSD licenses is reproduced below. 8 | 9 | ------------------------------------------------------------------------------- 10 | The "New" BSD License: 11 | ********************** 12 | 13 | Copyright (c) 2005-2012, Eugene Lazutkin 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | * Redistributions in binary form must reproduce the above copyright notice, 22 | this list of conditions and the following disclaimer in the documentation 23 | and/or other materials provided with the distribution. 24 | * Neither the name of Eugene Lazutkin nor the names of other contributors 25 | may be used to endorse or promote products derived from this software 26 | without specific prior written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 29 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 30 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 31 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 32 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 33 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 34 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 35 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 36 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | ------------------------------------------------------------------------------- 40 | The Academic Free License, v. 2.1: 41 | ********************************** 42 | 43 | This Academic Free License (the "License") applies to any original work of 44 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 45 | following notice immediately following the copyright notice for the Original 46 | Work: 47 | 48 | Licensed under the Academic Free License version 2.1 49 | 50 | 1) Grant of Copyright License. Licensor hereby grants You a world-wide, 51 | royalty-free, non-exclusive, perpetual, sublicenseable license to do the 52 | following: 53 | 54 | a) to reproduce the Original Work in copies; 55 | 56 | b) to prepare derivative works ("Derivative Works") based upon the Original 57 | Work; 58 | 59 | c) to distribute copies of the Original Work and Derivative Works to the 60 | public; 61 | 62 | d) to perform the Original Work publicly; and 63 | 64 | e) to display the Original Work publicly. 65 | 66 | 2) Grant of Patent License. Licensor hereby grants You a world-wide, 67 | royalty-free, non-exclusive, perpetual, sublicenseable license, under patent 68 | claims owned or controlled by the Licensor that are embodied in the Original 69 | Work as furnished by the Licensor, to make, use, sell and offer for sale the 70 | Original Work and Derivative Works. 71 | 72 | 3) Grant of Source Code License. The term "Source Code" means the preferred 73 | form of the Original Work for making modifications to it and all available 74 | documentation describing how to modify the Original Work. Licensor hereby 75 | agrees to provide a machine-readable copy of the Source Code of the Original 76 | Work along with each copy of the Original Work that Licensor distributes. 77 | Licensor reserves the right to satisfy this obligation by placing a 78 | machine-readable copy of the Source Code in an information repository 79 | reasonably calculated to permit inexpensive and convenient access by You for as 80 | long as Licensor continues to distribute the Original Work, and by publishing 81 | the address of that information repository in a notice immediately following 82 | the copyright notice that applies to the Original Work. 83 | 84 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 85 | of any contributors to the Original Work, nor any of their trademarks or 86 | service marks, may be used to endorse or promote products derived from this 87 | Original Work without express prior written permission of the Licensor. Nothing 88 | in this License shall be deemed to grant any rights to trademarks, copyrights, 89 | patents, trade secrets or any other intellectual property of Licensor except as 90 | expressly stated herein. No patent license is granted to make, use, sell or 91 | offer to sell embodiments of any patent claims other than the licensed claims 92 | defined in Section 2. No right is granted to the trademarks of Licensor even if 93 | such marks are included in the Original Work. Nothing in this License shall be 94 | interpreted to prohibit Licensor from licensing under different terms from this 95 | License any Original Work that Licensor otherwise would have a right to 96 | license. 97 | 98 | 5) This section intentionally omitted. 99 | 100 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 101 | Works that You create, all copyright, patent or trademark notices from the 102 | Source Code of the Original Work, as well as any notices of licensing and any 103 | descriptive text identified therein as an "Attribution Notice." You must cause 104 | the Source Code for any Derivative Works that You create to carry a prominent 105 | Attribution Notice reasonably calculated to inform recipients that You have 106 | modified the Original Work. 107 | 108 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 109 | the copyright in and to the Original Work and the patent rights granted herein 110 | by Licensor are owned by the Licensor or are sublicensed to You under the terms 111 | of this License with the permission of the contributor(s) of those copyrights 112 | and patent rights. Except as expressly stated in the immediately proceeding 113 | sentence, the Original Work is provided under this License on an "AS IS" BASIS 114 | and WITHOUT WARRANTY, either express or implied, including, without limitation, 115 | the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR 116 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. 117 | This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No 118 | license to Original Work is granted hereunder except under this disclaimer. 119 | 120 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 121 | whether in tort (including negligence), contract, or otherwise, shall the 122 | Licensor be liable to any person for any direct, indirect, special, incidental, 123 | or consequential damages of any character arising as a result of this License 124 | or the use of the Original Work including, without limitation, damages for loss 125 | of goodwill, work stoppage, computer failure or malfunction, or any and all 126 | other commercial damages or losses. This limitation of liability shall not 127 | apply to liability for death or personal injury resulting from Licensor's 128 | negligence to the extent applicable law prohibits such limitation. Some 129 | jurisdictions do not allow the exclusion or limitation of incidental or 130 | consequential damages, so this exclusion and limitation may not apply to You. 131 | 132 | 9) Acceptance and Termination. If You distribute copies of the Original Work or 133 | a Derivative Work, You must make a reasonable effort under the circumstances to 134 | obtain the express assent of recipients to the terms of this License. Nothing 135 | else but this License (or another written agreement between Licensor and You) 136 | grants You permission to create Derivative Works based upon the Original Work 137 | or to exercise any of the rights granted in Section 1 herein, and any attempt 138 | to do so except under the terms of this License (or another written agreement 139 | between Licensor and You) is expressly prohibited by U.S. copyright law, the 140 | equivalent laws of other countries, and by international treaty. Therefore, by 141 | exercising any of the rights granted to You in Section 1 herein, You indicate 142 | Your acceptance of this License and all of its terms and conditions. 143 | 144 | 10) Termination for Patent Action. This License shall terminate automatically 145 | and You may no longer exercise any of the rights granted to You by this License 146 | as of the date You commence an action, including a cross-claim or counterclaim, 147 | against Licensor or any licensee alleging that the Original Work infringes a 148 | patent. This termination provision shall not apply for an action alleging 149 | patent infringement by combinations of the Original Work with other software or 150 | hardware. 151 | 152 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 153 | License may be brought only in the courts of a jurisdiction wherein the 154 | Licensor resides or in which Licensor conducts its primary business, and under 155 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 156 | application of the United Nations Convention on Contracts for the International 157 | Sale of Goods is expressly excluded. Any use of the Original Work outside the 158 | scope of this License or after its termination shall be subject to the 159 | requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et 160 | seq., the equivalent laws of other countries, and international treaty. This 161 | section shall survive the termination of this License. 162 | 163 | 12) Attorneys Fees. In any action to enforce the terms of this License or 164 | seeking damages relating thereto, the prevailing party shall be entitled to 165 | recover its costs and expenses, including, without limitation, reasonable 166 | attorneys' fees and costs incurred in connection with such action, including 167 | any appeal of such action. This section shall survive the termination of this 168 | License. 169 | 170 | 13) Miscellaneous. This License represents the complete agreement concerning 171 | the subject matter hereof. If any provision of this License is held to be 172 | unenforceable, such provision shall be reformed only to the extent necessary to 173 | make it enforceable. 174 | 175 | 14) Definition of "You" in This License. "You" throughout this License, whether 176 | in upper or lower case, means an individual or a legal entity exercising rights 177 | under, and complying with all of the terms of, this License. For legal 178 | entities, "You" includes any entity that controls, is controlled by, or is 179 | under common control with you. For purposes of this definition, "control" means 180 | (i) the power, direct or indirect, to cause the direction or management of such 181 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 182 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of such 183 | entity. 184 | 185 | 15) Right to Use. You may use the Original Work in all ways not otherwise 186 | restricted or conditioned by this License or by law, and Licensor promises not 187 | to interfere with or be responsible for such uses by You. 188 | 189 | This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. 190 | Permission is hereby granted to copy and distribute this license without 191 | modification. This license may not be modified without the express written 192 | permission of its copyright owner. 193 | -------------------------------------------------------------------------------- /_base/connect.js: -------------------------------------------------------------------------------- 1 | define(["./kernel", "../on", "../topic", "../aspect", "./event", "../mouse", "./sniff", "./lang", "../keys"], function(dojo, on, hub, aspect, eventModule, mouse, has, lang){ 2 | // module: 3 | // dojo/_base/connect 4 | 5 | has.add("events-keypress-typed", function(){ // keypresses should only occur a printable character is hit 6 | var testKeyEvent = {charCode: 0}; 7 | try{ 8 | testKeyEvent = document.createEvent("KeyboardEvent"); 9 | (testKeyEvent.initKeyboardEvent || testKeyEvent.initKeyEvent).call(testKeyEvent, "keypress", true, true, null, false, false, false, false, 9, 3); 10 | }catch(e){} 11 | return testKeyEvent.charCode == 0 && !has("opera"); 12 | }); 13 | 14 | function connect_(obj, event, context, method, dontFix){ 15 | method = lang.hitch(context, method); 16 | if(!obj || !(obj.addEventListener || obj.attachEvent)){ 17 | // it is a not a DOM node and we are using the dojo.connect style of treating a 18 | // method like an event, must go right to aspect 19 | return aspect.after(obj || dojo.global, event, method, true); 20 | } 21 | if(typeof event == "string" && event.substring(0, 2) == "on"){ 22 | event = event.substring(2); 23 | } 24 | if(!obj){ 25 | obj = dojo.global; 26 | } 27 | if(!dontFix){ 28 | switch(event){ 29 | // dojo.connect has special handling for these event types 30 | case "keypress": 31 | event = keypress; 32 | break; 33 | case "mouseenter": 34 | event = mouse.enter; 35 | break; 36 | case "mouseleave": 37 | event = mouse.leave; 38 | break; 39 | } 40 | } 41 | return on(obj, event, method, dontFix); 42 | } 43 | 44 | var _punctMap = { 45 | 106:42, 46 | 111:47, 47 | 186:59, 48 | 187:43, 49 | 188:44, 50 | 189:45, 51 | 190:46, 52 | 191:47, 53 | 192:96, 54 | 219:91, 55 | 220:92, 56 | 221:93, 57 | 222:39, 58 | 229:113 59 | }; 60 | var evtCopyKey = has("mac") ? "metaKey" : "ctrlKey"; 61 | 62 | 63 | var _synthesizeEvent = function(evt, props){ 64 | var faux = lang.mixin({}, evt, props); 65 | setKeyChar(faux); 66 | // FIXME: would prefer to use lang.hitch: lang.hitch(evt, evt.preventDefault); 67 | // but it throws an error when preventDefault is invoked on Safari 68 | // does Event.preventDefault not support "apply" on Safari? 69 | faux.preventDefault = function(){ evt.preventDefault(); }; 70 | faux.stopPropagation = function(){ evt.stopPropagation(); }; 71 | return faux; 72 | }; 73 | function setKeyChar(evt){ 74 | evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : ''; 75 | evt.charOrCode = evt.keyChar || evt.keyCode; 76 | } 77 | var keypress; 78 | if(has("events-keypress-typed")){ 79 | // this emulates Firefox's keypress behavior where every keydown can correspond to a keypress 80 | var _trySetKeyCode = function(e, code){ 81 | try{ 82 | // squelch errors when keyCode is read-only 83 | // (e.g. if keyCode is ctrl or shift) 84 | return (e.keyCode = code); 85 | }catch(e){ 86 | return 0; 87 | } 88 | }; 89 | keypress = function(object, listener){ 90 | var keydownSignal = on(object, "keydown", function(evt){ 91 | // munge key/charCode 92 | var k=evt.keyCode; 93 | // These are Windows Virtual Key Codes 94 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/VirtualKeyCodes.asp 95 | var unprintable = (k!=13) && k!=32 && (k!=27||!has("ie")) && (k<48||k>90) && (k<96||k>111) && (k<186||k>192) && (k<219||k>222) && k!=229; 96 | // synthesize keypress for most unprintables and CTRL-keys 97 | if(unprintable||evt.ctrlKey){ 98 | var c = unprintable ? 0 : k; 99 | if(evt.ctrlKey){ 100 | if(k==3 || k==13){ 101 | return listener.call(evt.currentTarget, evt); // IE will post CTRL-BREAK, CTRL-ENTER as keypress natively 102 | }else if(c>95 && c<106){ 103 | c -= 48; // map CTRL-[numpad 0-9] to ASCII 104 | }else if((!evt.shiftKey)&&(c>=65&&c<=90)){ 105 | c += 32; // map CTRL-[A-Z] to lowercase 106 | }else{ 107 | c = _punctMap[c] || c; // map other problematic CTRL combinations to ASCII 108 | } 109 | } 110 | // simulate a keypress event 111 | var faux = _synthesizeEvent(evt, {type: 'keypress', faux: true, charCode: c}); 112 | listener.call(evt.currentTarget, faux); 113 | if(has("ie")){ 114 | _trySetKeyCode(evt, faux.keyCode); 115 | } 116 | } 117 | }); 118 | var keypressSignal = on(object, "keypress", function(evt){ 119 | var c = evt.charCode; 120 | c = c>=32 ? c : 0; 121 | evt = _synthesizeEvent(evt, {charCode: c, faux: true}); 122 | return listener.call(this, evt); 123 | }); 124 | return { 125 | remove: function(){ 126 | keydownSignal.remove(); 127 | keypressSignal.remove(); 128 | } 129 | }; 130 | }; 131 | }else{ 132 | if(has("opera")){ 133 | keypress = function(object, listener){ 134 | return on(object, "keypress", function(evt){ 135 | var c = evt.which; 136 | if(c==3){ 137 | c=99; // Mozilla maps CTRL-BREAK to CTRL-c 138 | } 139 | // can't trap some keys at all, like INSERT and DELETE 140 | // there is no differentiating info between DELETE and ".", or INSERT and "-" 141 | c = c<32 && !evt.shiftKey ? 0 : c; 142 | if(evt.ctrlKey && !evt.shiftKey && c>=65 && c<=90){ 143 | // lowercase CTRL-[A-Z] keys 144 | c += 32; 145 | } 146 | return listener.call(this, _synthesizeEvent(evt, { charCode: c })); 147 | }); 148 | }; 149 | }else{ 150 | keypress = function(object, listener){ 151 | return on(object, "keypress", function(evt){ 152 | setKeyChar(evt); 153 | return listener.call(this, evt); 154 | }); 155 | }; 156 | } 157 | } 158 | 159 | var connect = { 160 | // summary: 161 | // This module defines the dojo.connect API. 162 | // This modules also provides keyboard event handling helpers. 163 | // This module exports an extension event for emulating Firefox's keypress handling. 164 | // However, this extension event exists primarily for backwards compatibility and 165 | // is not recommended. WebKit and IE uses an alternate keypress handling (only 166 | // firing for printable characters, to distinguish from keydown events), and most 167 | // consider the WebKit/IE behavior more desirable. 168 | 169 | _keypress:keypress, 170 | 171 | connect:function(obj, event, context, method, dontFix){ 172 | // summary: 173 | // `dojo.connect` is a deprecated event handling and delegation method in 174 | // Dojo. It allows one function to "listen in" on the execution of 175 | // any other, triggering the second whenever the first is called. Many 176 | // listeners may be attached to a function, and source functions may 177 | // be either regular function calls or DOM events. 178 | // 179 | // description: 180 | // Connects listeners to actions, so that after event fires, a 181 | // listener is called with the same arguments passed to the original 182 | // function. 183 | // 184 | // Since `dojo.connect` allows the source of events to be either a 185 | // "regular" JavaScript function or a DOM event, it provides a uniform 186 | // interface for listening to all the types of events that an 187 | // application is likely to deal with though a single, unified 188 | // interface. DOM programmers may want to think of it as 189 | // "addEventListener for everything and anything". 190 | // 191 | // When setting up a connection, the `event` parameter must be a 192 | // string that is the name of the method/event to be listened for. If 193 | // `obj` is null, `kernel.global` is assumed, meaning that connections 194 | // to global methods are supported but also that you may inadvertently 195 | // connect to a global by passing an incorrect object name or invalid 196 | // reference. 197 | // 198 | // `dojo.connect` generally is forgiving. If you pass the name of a 199 | // function or method that does not yet exist on `obj`, connect will 200 | // not fail, but will instead set up a stub method. Similarly, null 201 | // arguments may simply be omitted such that fewer than 4 arguments 202 | // may be required to set up a connection See the examples for details. 203 | // 204 | // The return value is a handle that is needed to 205 | // remove this connection with `dojo.disconnect`. 206 | // 207 | // obj: Object? 208 | // The source object for the event function. 209 | // Defaults to `kernel.global` if null. 210 | // If obj is a DOM node, the connection is delegated 211 | // to the DOM event manager (unless dontFix is true). 212 | // 213 | // event: String 214 | // String name of the event function in obj. 215 | // I.e. identifies a property `obj[event]`. 216 | // 217 | // context: Object|null 218 | // The object that method will receive as "this". 219 | // 220 | // If context is null and method is a function, then method 221 | // inherits the context of event. 222 | // 223 | // If method is a string then context must be the source 224 | // object object for method (context[method]). If context is null, 225 | // kernel.global is used. 226 | // 227 | // method: String|Function 228 | // A function reference, or name of a function in context. 229 | // The function identified by method fires after event does. 230 | // method receives the same arguments as the event. 231 | // See context argument comments for information on method's scope. 232 | // 233 | // dontFix: Boolean? 234 | // If obj is a DOM node, set dontFix to true to prevent delegation 235 | // of this connection to the DOM event manager. 236 | // 237 | // example: 238 | // When obj.onchange(), do ui.update(): 239 | // | dojo.connect(obj, "onchange", ui, "update"); 240 | // | dojo.connect(obj, "onchange", ui, ui.update); // same 241 | // 242 | // example: 243 | // Using return value for disconnect: 244 | // | var link = dojo.connect(obj, "onchange", ui, "update"); 245 | // | ... 246 | // | dojo.disconnect(link); 247 | // 248 | // example: 249 | // When onglobalevent executes, watcher.handler is invoked: 250 | // | dojo.connect(null, "onglobalevent", watcher, "handler"); 251 | // 252 | // example: 253 | // When ob.onCustomEvent executes, customEventHandler is invoked: 254 | // | dojo.connect(ob, "onCustomEvent", null, "customEventHandler"); 255 | // | dojo.connect(ob, "onCustomEvent", "customEventHandler"); // same 256 | // 257 | // example: 258 | // When ob.onCustomEvent executes, customEventHandler is invoked 259 | // with the same scope (this): 260 | // | dojo.connect(ob, "onCustomEvent", null, customEventHandler); 261 | // | dojo.connect(ob, "onCustomEvent", customEventHandler); // same 262 | // 263 | // example: 264 | // When globalEvent executes, globalHandler is invoked 265 | // with the same scope (this): 266 | // | dojo.connect(null, "globalEvent", null, globalHandler); 267 | // | dojo.connect("globalEvent", globalHandler); // same 268 | 269 | // normalize arguments 270 | var a=arguments, args=[], i=0; 271 | // if a[0] is a String, obj was omitted 272 | args.push(typeof a[0] == "string" ? null : a[i++], a[i++]); 273 | // if the arg-after-next is a String or Function, context was NOT omitted 274 | var a1 = a[i+1]; 275 | args.push(typeof a1 == "string" || typeof a1 == "function" ? a[i++] : null, a[i++]); 276 | // absorb any additional arguments 277 | for(var l=a.length; i lastTouch + 1000 ? evt : sink.noValue; 34 | }); 35 | }; 36 | }else{ 37 | // Avoid creating listeners for touch events on performance sensitive 38 | // older browsers like IE6 39 | return function(node){ 40 | return on(node, mouseType); 41 | } 42 | } 43 | } 44 | 45 | function marked(/*DOMNode*/ node){ 46 | // Test if a node or its ancestor has been marked with the dojoClick property 47 | // to indicate special processing, 48 | do{ 49 | if(node.dojoClick){ return node.dojoClick; } 50 | }while(node = node.parentNode); 51 | } 52 | 53 | function doClicks(e, moveType, endType){ 54 | // summary: 55 | // Setup touch listeners to generate synthetic clicks immediately 56 | // (rather than waiting for the browser to generate clicks after 57 | // the double-tap delay) and consistently (regardless of whether 58 | // event.preventDefault() was called in an event listener. 59 | // Synthetic clicks are generated only if a node or one of 60 | // its ancestors has its dojoClick property set to truthy. 61 | 62 | clickTracker = !e.target.disabled && marked(e.target); // click threshold = true, number or x/y object 63 | if(clickTracker){ 64 | clickTarget = e.target; 65 | clickX = e.touches ? e.touches[0].pageX : e.clientX; 66 | clickY = e.touches ? e.touches[0].pageY : e.clientY; 67 | clickDx = (typeof clickTracker == "object" ? clickTracker.x : (typeof clickTracker == "number" ? clickTracker : 0)) || 4; 68 | clickDy = (typeof clickTracker == "object" ? clickTracker.y : (typeof clickTracker == "number" ? clickTracker : 0)) || 4; 69 | 70 | // add move/end handlers only the first time a node with dojoClick is seen, 71 | // so we don't add too much overhead when dojoClick is never set. 72 | if(!clicksInited){ 73 | clicksInited = true; 74 | 75 | win.doc.addEventListener(moveType, function(e){ 76 | clickTracker = clickTracker && 77 | e.target == clickTarget && 78 | Math.abs((e.touches ? e.touches[0].pageX : e.clientX) - clickX) <= clickDx && 79 | Math.abs((e.touches ? e.touches[0].pageY : e.clientY) - clickY) <= clickDy; 80 | }, true); 81 | 82 | win.doc.addEventListener(endType, function(e){ 83 | if(clickTracker){ 84 | clickTime = (new Date()).getTime(); 85 | var target = e.target; 86 | if(target.tagName === "LABEL"){ 87 | // when clicking on a label, forward click to its associated input if any 88 | target = dom.byId(target.getAttribute("for")) || target; 89 | } 90 | setTimeout(function(){ 91 | emit(target, { 92 | type: "click", 93 | bubbles : true, 94 | cancelable : true, 95 | _dojo_click : true 96 | }, 0); // TODO: really??? 97 | }); 98 | } 99 | }, true); 100 | 101 | function stopNativeEvents(type){ 102 | win.doc.addEventListener(type, function(e){ 103 | // Stop native events when we emitted our own click event. Note that the native click may occur 104 | // on a different node than the synthetic click event was generated on. For example, 105 | // click on a menu item, causing the menu to disappear, and then (~300ms later) the browser 106 | // sends a click event to the node that was *underneath* the menu. So stop all native events 107 | // sent shortly after ours, similar to what is done in dualEvent. 108 | // The INPUT.dijitOffScreen test is for offscreen inputs used in dijit/form/Button, on which 109 | // we call click() explicitly, we don't want to stop this event. 110 | if(!e._dojo_click && 111 | (new Date()).getTime() <= clickTime + 1000 && 112 | !(e.target.tagName == "INPUT" && cls.contains(e.target, "dijitOffScreen"))){ 113 | e.stopPropagation(); 114 | e.stopImmediatePropagation && e.stopImmediatePropagation(); 115 | if(type == "click" && (e.target.tagName != "INPUT" || e.target.type == "radio" || e.target.type == "checkbox") 116 | && e.target.tagName != "TEXTAREA"){ 117 | // preventDefault() breaks textual s on android, keyboard doesn't popup, 118 | // but it is still needed for checkboxes and radio buttons, otherwise in some cases 119 | // the checked state becomes inconsistent with the widget's state 120 | e.preventDefault(); 121 | } 122 | } 123 | }, true); 124 | } 125 | 126 | stopNativeEvents("click"); 127 | 128 | // We also stop mousedown/up since these would be sent well after with our "fast" click (300ms), 129 | // which can confuse some dijit widgets. 130 | stopNativeEvents("mousedown"); 131 | stopNativeEvents("mouseup"); 132 | } 133 | } 134 | } 135 | 136 | var hoveredNode; 137 | 138 | if(hasTouch){ 139 | if(msPointer){ 140 | // MSPointer (IE10+) already has support for over and out, so we just need to init click support 141 | domReady(function(){ 142 | win.doc.addEventListener("MSPointerDown", function(evt){ 143 | doClicks(evt, "MSPointerMove", "MSPointerUp"); 144 | }, true); 145 | }); 146 | }else{ 147 | domReady(function(){ 148 | // Keep track of currently hovered node 149 | hoveredNode = win.body(); // currently hovered node 150 | 151 | win.doc.addEventListener("touchstart", function(evt){ 152 | lastTouch = (new Date()).getTime(); 153 | 154 | // Precede touchstart event with touch.over event. DnD depends on this. 155 | // Use addEventListener(cb, true) to run cb before any touchstart handlers on node run, 156 | // and to ensure this code runs even if the listener on the node does event.stop(). 157 | var oldNode = hoveredNode; 158 | hoveredNode = evt.target; 159 | emit(oldNode, { 160 | type: "dojotouchout", 161 | relatedTarget: hoveredNode, 162 | bubbles: true 163 | }); 164 | emit(hoveredNode, { 165 | type: "dojotouchover", 166 | relatedTarget: oldNode, 167 | bubbles: true 168 | }); 169 | 170 | doClicks(evt, "touchmove", "touchend"); // init click generation 171 | }, true); 172 | 173 | function copyEventProps(type, evt){ 174 | // Make copy of event object and also set bubbles:true. Used when calling emit(). 175 | var props = Object.create(evt); 176 | props.type = type; 177 | props.bubbles = true; 178 | 179 | if(has("ios") >= 6){ 180 | // On iOS6 "touches" became a non-enumerable property, which 181 | // is not hit by for...in. Ditto for the other properties below. 182 | props.touches = evt.touches; 183 | props.altKey = evt.altKey; 184 | props.changedTouches = evt.changedTouches; 185 | props.ctrlKey = evt.ctrlKey; 186 | props.metaKey = evt.metaKey; 187 | props.shiftKey = evt.shiftKey; 188 | props.targetTouches = evt.targetTouches; 189 | } 190 | 191 | return props; 192 | } 193 | 194 | on(win.doc, "touchmove", function(evt){ 195 | lastTouch = new Date().getTime(); 196 | 197 | var newNode = win.doc.elementFromPoint( 198 | evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords 199 | evt.pageY - (ios4 ? 0 : win.global.pageYOffset) 200 | ); 201 | 202 | if(newNode){ 203 | // Fire synthetic touchover and touchout events on nodes since the browser won't do it natively. 204 | if(hoveredNode !== newNode){ 205 | // touch out on the old node 206 | emit(hoveredNode, { 207 | type: "dojotouchout", 208 | relatedTarget: newNode, 209 | bubbles: true 210 | }); 211 | 212 | // touchover on the new node 213 | emit(newNode, { 214 | type: "dojotouchover", 215 | relatedTarget: hoveredNode, 216 | bubbles: true 217 | }); 218 | 219 | hoveredNode = newNode; 220 | } 221 | 222 | // Unlike a listener on "touchmove", on(node, "dojotouchmove", listener) 223 | // fires when the finger drags over the specified node, regardless of 224 | // which node the touch started on. 225 | emit(newNode, copyEventProps("dojotouchmove", evt)); 226 | } 227 | 228 | return evt; 229 | }); 230 | 231 | // Fire a dojotouchend event on the node where the finger was before it was removed from the screen. 232 | // This is different than the native touchend, which fires on the node where the drag started. 233 | on(win.doc, "touchend", function(evt){ 234 | lastTouch = new Date().getTime(); 235 | var node = win.doc.elementFromPoint( 236 | evt.pageX - (ios4 ? 0 : win.global.pageXOffset), // iOS 4 expects page coords 237 | evt.pageY - (ios4 ? 0 : win.global.pageYOffset) 238 | ) || win.body(); // if out of the screen 239 | 240 | emit(node, copyEventProps("dojotouchend", evt)); 241 | return evt; 242 | }); 243 | }); 244 | } 245 | } 246 | 247 | /*===== 248 | touch = { 249 | // summary: 250 | // This module provides unified touch event handlers by exporting 251 | // press, move, release and cancel which can also run well on desktop. 252 | // Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html 253 | // Also, if the dojoClick property is set to true on a DOM node, dojo/touch generates 254 | // click events immediately for this node and its descendants, to avoid the 255 | // delay before native browser click events, and regardless of whether evt.preventDefault() 256 | // was called in a touch.press event listener. 257 | // 258 | // example: 259 | // Used with dojo.on 260 | // | define(["dojo/on", "dojo/touch"], function(on, touch){ 261 | // | on(node, touch.press, function(e){}); 262 | // | on(node, touch.move, function(e){}); 263 | // | on(node, touch.release, function(e){}); 264 | // | on(node, touch.cancel, function(e){}); 265 | // example: 266 | // Used with touch.* directly 267 | // | touch.press(node, function(e){}); 268 | // | touch.move(node, function(e){}); 269 | // | touch.release(node, function(e){}); 270 | // | touch.cancel(node, function(e){}); 271 | // example: 272 | // Have dojo/touch generate clicks without delay, with a default move threshold of 4 pixels 273 | // | node.dojoClick = true; 274 | // example: 275 | // Have dojo/touch generate clicks without delay, with a move threshold of 10 pixels horizontally and vertically 276 | // | node.dojoClick = 10; 277 | // example: 278 | // Have dojo/touch generate clicks without delay, with a move threshold of 50 pixels horizontally and 10 pixels vertically 279 | // | node.dojoClick = {x:50, y:5}; 280 | 281 | 282 | press: function(node, listener){ 283 | // summary: 284 | // Register a listener to 'touchstart'|'mousedown' for the given node 285 | // node: Dom 286 | // Target node to listen to 287 | // listener: Function 288 | // Callback function 289 | // returns: 290 | // A handle which will be used to remove the listener by handle.remove() 291 | }, 292 | move: function(node, listener){ 293 | // summary: 294 | // Register a listener that fires when the mouse cursor or a finger is dragged over the given node. 295 | // node: Dom 296 | // Target node to listen to 297 | // listener: Function 298 | // Callback function 299 | // returns: 300 | // A handle which will be used to remove the listener by handle.remove() 301 | }, 302 | release: function(node, listener){ 303 | // summary: 304 | // Register a listener to releasing the mouse button while the cursor is over the given node 305 | // (i.e. "mouseup") or for removing the finger from the screen while touching the given node. 306 | // node: Dom 307 | // Target node to listen to 308 | // listener: Function 309 | // Callback function 310 | // returns: 311 | // A handle which will be used to remove the listener by handle.remove() 312 | }, 313 | cancel: function(node, listener){ 314 | // summary: 315 | // Register a listener to 'touchcancel'|'mouseleave' for the given node 316 | // node: Dom 317 | // Target node to listen to 318 | // listener: Function 319 | // Callback function 320 | // returns: 321 | // A handle which will be used to remove the listener by handle.remove() 322 | }, 323 | over: function(node, listener){ 324 | // summary: 325 | // Register a listener to 'mouseover' or touch equivalent for the given node 326 | // node: Dom 327 | // Target node to listen to 328 | // listener: Function 329 | // Callback function 330 | // returns: 331 | // A handle which will be used to remove the listener by handle.remove() 332 | }, 333 | out: function(node, listener){ 334 | // summary: 335 | // Register a listener to 'mouseout' or touch equivalent for the given node 336 | // node: Dom 337 | // Target node to listen to 338 | // listener: Function 339 | // Callback function 340 | // returns: 341 | // A handle which will be used to remove the listener by handle.remove() 342 | }, 343 | enter: function(node, listener){ 344 | // summary: 345 | // Register a listener to mouse.enter or touch equivalent for the given node 346 | // node: Dom 347 | // Target node to listen to 348 | // listener: Function 349 | // Callback function 350 | // returns: 351 | // A handle which will be used to remove the listener by handle.remove() 352 | }, 353 | leave: function(node, listener){ 354 | // summary: 355 | // Register a listener to mouse.leave or touch equivalent for the given node 356 | // node: Dom 357 | // Target node to listen to 358 | // listener: Function 359 | // Callback function 360 | // returns: 361 | // A handle which will be used to remove the listener by handle.remove() 362 | } 363 | }; 364 | =====*/ 365 | 366 | //device neutral events - touch.press|move|release|cancel/over/out 367 | return { 368 | press: dualEvent("mousedown", "touchstart", "MSPointerDown"), 369 | move: dualEvent("mousemove", "dojotouchmove", "MSPointerMove"), 370 | release: dualEvent("mouseup", "dojotouchend", "MSPointerUp"), 371 | cancel: dualEvent(mouse.leave, "touchcancel", hasTouch ? "MSPointerCancel" : null), 372 | over: dualEvent("mouseover", "dojotouchover", "MSPointerOver"), 373 | out: dualEvent("mouseout", "dojotouchout", "MSPointerOut"), 374 | enter: mouse._eventHandler(dualEvent("mouseover","dojotouchover", "MSPointerOver")), 375 | leave: mouse._eventHandler(dualEvent("mouseout", "dojotouchout", "MSPointerOut")) 376 | }; 377 | }); 378 | -------------------------------------------------------------------------------- /on.orig.js: -------------------------------------------------------------------------------- 1 | define(["./has!dom-addeventlistener?:./aspect", "./_base/kernel", "./sniff"], function(aspect, dojo, has){ 2 | 3 | "use strict"; 4 | if(has("dom")){ // check to make sure we are in a browser, this module should work anywhere 5 | var major = window.ScriptEngineMajorVersion; 6 | has.add("jscript", major && (major() + ScriptEngineMinorVersion() / 10)); 7 | has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this? 8 | has.add("event-stopimmediatepropagation", window.Event && !!window.Event.prototype && !!window.Event.prototype.stopImmediatePropagation); 9 | has.add("event-focusin", function(global, doc, element){ 10 | // All browsers except firefox support focusin, but too hard to feature test webkit since element.onfocusin 11 | // is undefined. Just return true for IE and use fallback path for other browsers. 12 | return !!element.attachEvent; 13 | }); 14 | } 15 | var on = function(target, type, listener, dontFix){ 16 | // summary: 17 | // A function that provides core event listening functionality. With this function 18 | // you can provide a target, event type, and listener to be notified of 19 | // future matching events that are fired. 20 | // target: Element|Object 21 | // This is the target object or DOM element that to receive events from 22 | // type: String|Function 23 | // This is the name of the event to listen for or an extension event type. 24 | // listener: Function 25 | // This is the function that should be called when the event fires. 26 | // returns: Object 27 | // An object with a remove() method that can be used to stop listening for this 28 | // event. 29 | // description: 30 | // To listen for "click" events on a button node, we can do: 31 | // | define(["dojo/on"], function(listen){ 32 | // | on(button, "click", clickHandler); 33 | // | ... 34 | // Evented JavaScript objects can also have their own events. 35 | // | var obj = new Evented; 36 | // | on(obj, "foo", fooHandler); 37 | // And then we could publish a "foo" event: 38 | // | on.emit(obj, "foo", {key: "value"}); 39 | // We can use extension events as well. For example, you could listen for a tap gesture: 40 | // | define(["dojo/on", "dojo/gesture/tap", function(listen, tap){ 41 | // | on(button, tap, tapHandler); 42 | // | ... 43 | // which would trigger fooHandler. Note that for a simple object this is equivalent to calling: 44 | // | obj.onfoo({key:"value"}); 45 | // If you use on.emit on a DOM node, it will use native event dispatching when possible. 46 | 47 | if(typeof target.on == "function" && typeof type != "function" && !target.nodeType){ 48 | // delegate to the target's on() method, so it can handle it's own listening if it wants (unless it 49 | // is DOM node and we may be dealing with jQuery or Prototype's incompatible addition to the 50 | // Element prototype 51 | return target.on(type, listener); 52 | } 53 | // delegate to main listener code 54 | return on.parse(target, type, listener, addListener, dontFix, this); 55 | }; 56 | on.pausable = function(target, type, listener, dontFix){ 57 | // summary: 58 | // This function acts the same as on(), but with pausable functionality. The 59 | // returned signal object has pause() and resume() functions. Calling the 60 | // pause() method will cause the listener to not be called for future events. Calling the 61 | // resume() method will cause the listener to again be called for future events. 62 | var paused; 63 | var signal = on(target, type, function(){ 64 | if(!paused){ 65 | return listener.apply(this, arguments); 66 | } 67 | }, dontFix); 68 | signal.pause = function(){ 69 | paused = true; 70 | }; 71 | signal.resume = function(){ 72 | paused = false; 73 | }; 74 | return signal; 75 | }; 76 | on.once = function(target, type, listener, dontFix){ 77 | // summary: 78 | // This function acts the same as on(), but will only call the listener once. The 79 | // listener will be called for the first 80 | // event that takes place and then listener will automatically be removed. 81 | var signal = on(target, type, function(){ 82 | // remove this listener 83 | signal.remove(); 84 | // proceed to call the listener 85 | return listener.apply(this, arguments); 86 | }); 87 | return signal; 88 | }; 89 | on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){ 90 | if(type.call){ 91 | // event handler function 92 | // on(node, touch.press, touchListener); 93 | return type.call(matchesTarget, target, listener); 94 | } 95 | 96 | if(type.indexOf(",") > -1){ 97 | // we allow comma delimited event names, so you can register for multiple events at once 98 | var events = type.split(/\s*,\s*/); 99 | var handles = []; 100 | var i = 0; 101 | var eventName; 102 | while(eventName = events[i++]){ 103 | handles.push(addListener(target, eventName, listener, dontFix, matchesTarget)); 104 | } 105 | handles.remove = function(){ 106 | for(var i = 0; i < handles.length; i++){ 107 | handles[i].remove(); 108 | } 109 | }; 110 | return handles; 111 | } 112 | return addListener(target, type, listener, dontFix, matchesTarget); 113 | }; 114 | var touchEvents = /^touch/; 115 | function addListener(target, type, listener, dontFix, matchesTarget){ 116 | // event delegation: 117 | var selector = type.match(/(.*):(.*)/); 118 | // if we have a selector:event, the last one is interpreted as an event, and we use event delegation 119 | if(selector){ 120 | type = selector[2]; 121 | selector = selector[1]; 122 | // create the extension event for selectors and directly call it 123 | return on.selector(selector, type).call(matchesTarget, target, listener); 124 | } 125 | // test to see if it a touch event right now, so we don't have to do it every time it fires 126 | if(has("touch")){ 127 | if(touchEvents.test(type)){ 128 | // touch event, fix it 129 | listener = fixTouchListener(listener); 130 | } 131 | if(!has("event-orientationchange") && (type == "orientationchange")){ 132 | //"orientationchange" not supported <= Android 2.1, 133 | //but works through "resize" on window 134 | type = "resize"; 135 | target = window; 136 | listener = fixTouchListener(listener); 137 | } 138 | } 139 | if(addStopImmediate){ 140 | // add stopImmediatePropagation if it doesn't exist 141 | listener = addStopImmediate(listener); 142 | } 143 | // normal path, the target is |this| 144 | if(target.addEventListener){ 145 | // 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) 146 | // check for capture conversions 147 | var capture = type in captures, 148 | adjustedType = capture ? captures[type] : type; 149 | target.addEventListener(adjustedType, listener, capture); 150 | // create and return the signal 151 | return { 152 | remove: function(){ 153 | target.removeEventListener(adjustedType, listener, capture); 154 | } 155 | }; 156 | } 157 | type = "on" + type; 158 | if(fixAttach && target.attachEvent){ 159 | return fixAttach(target, type, listener); 160 | } 161 | throw new Error("Target must be an event emitter"); 162 | } 163 | 164 | on.selector = function(selector, eventType, children){ 165 | // summary: 166 | // Creates a new extension event with event delegation. This is based on 167 | // the provided event type (can be extension event) that 168 | // only calls the listener when the CSS selector matches the target of the event. 169 | // 170 | // The application must require() an appropriate level of dojo/query to handle the selector. 171 | // selector: 172 | // The CSS selector to use for filter events and determine the |this| of the event listener. 173 | // eventType: 174 | // The event to listen for 175 | // children: 176 | // Indicates if children elements of the selector should be allowed. This defaults to 177 | // true 178 | // example: 179 | // | require(["dojo/on", "dojo/mouse", "dojo/query!css2"], function(listen, mouse){ 180 | // | on(node, on.selector(".my-class", mouse.enter), handlerForMyHover); 181 | return function(target, listener){ 182 | // if the selector is function, use it to select the node, otherwise use the matches method 183 | var matchesTarget = typeof selector == "function" ? {matches: selector} : this, 184 | bubble = eventType.bubble; 185 | function select(eventTarget){ 186 | // see if we have a valid matchesTarget or default to dojo.query 187 | matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query; 188 | // there is a selector, so make sure it matches 189 | while(!matchesTarget.matches(eventTarget, selector, target)){ 190 | if(eventTarget == target || children === false || !(eventTarget = eventTarget.parentNode) || eventTarget.nodeType != 1){ // intentional assignment 191 | return; 192 | } 193 | } 194 | return eventTarget; 195 | } 196 | if(bubble){ 197 | // 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 198 | return on(target, bubble(select), listener); 199 | } 200 | // standard event delegation 201 | return on(target, eventType, function(event){ 202 | // call select to see if we match 203 | var eventTarget = select(event.target); 204 | // if it matches we call the listener 205 | return eventTarget && listener.call(eventTarget, event); 206 | }); 207 | }; 208 | }; 209 | 210 | function syntheticPreventDefault(){ 211 | this.cancelable = false; 212 | this.defaultPrevented = true; 213 | } 214 | function syntheticStopPropagation(){ 215 | this.bubbles = false; 216 | } 217 | var slice = [].slice, 218 | syntheticDispatch = on.emit = function(target, type, event){ 219 | // summary: 220 | // Fires an event on the target object. 221 | // target: 222 | // The target object to fire the event on. This can be a DOM element or a plain 223 | // JS object. If the target is a DOM element, native event emitting mechanisms 224 | // are used when possible. 225 | // type: 226 | // The event type name. You can emulate standard native events like "click" and 227 | // "mouseover" or create custom events like "open" or "finish". 228 | // event: 229 | // An object that provides the properties for the event. See https://developer.mozilla.org/en/DOM/event.initEvent 230 | // for some of the properties. These properties are copied to the event object. 231 | // Of particular importance are the cancelable and bubbles properties. The 232 | // cancelable property indicates whether or not the event has a default action 233 | // that can be cancelled. The event is cancelled by calling preventDefault() on 234 | // the event object. The bubbles property indicates whether or not the 235 | // event will bubble up the DOM tree. If bubbles is true, the event will be called 236 | // on the target and then each parent successively until the top of the tree 237 | // is reached or stopPropagation() is called. Both bubbles and cancelable 238 | // default to false. 239 | // returns: 240 | // If the event is cancelable and the event is not cancelled, 241 | // emit will return true. If the event is cancelable and the event is cancelled, 242 | // emit will return false. 243 | // details: 244 | // Note that this is designed to emit events for listeners registered through 245 | // dojo/on. It should actually work with any event listener except those 246 | // added through IE's attachEvent (IE8 and below's non-W3C event emitting 247 | // doesn't support custom event types). It should work with all events registered 248 | // through dojo/on. Also note that the emit method does do any default 249 | // action, it only returns a value to indicate if the default action should take 250 | // place. For example, emitting a keypress event would not cause a character 251 | // to appear in a textbox. 252 | // example: 253 | // To fire our own click event 254 | // | on.emit(dojo.byId("button"), "click", { 255 | // | cancelable: true, 256 | // | bubbles: true, 257 | // | screenX: 33, 258 | // | screenY: 44 259 | // | }); 260 | // We can also fire our own custom events: 261 | // | on.emit(dojo.byId("slider"), "slide", { 262 | // | cancelable: true, 263 | // | bubbles: true, 264 | // | direction: "left-to-right" 265 | // | }); 266 | var args = slice.call(arguments, 2); 267 | var method = "on" + type; 268 | if("parentNode" in target){ 269 | // node (or node-like), create event controller methods 270 | var newEvent = args[0] = {}; 271 | for(var i in event){ 272 | newEvent[i] = event[i]; 273 | } 274 | newEvent.preventDefault = syntheticPreventDefault; 275 | newEvent.stopPropagation = syntheticStopPropagation; 276 | newEvent.target = target; 277 | newEvent.type = type; 278 | event = newEvent; 279 | } 280 | do{ 281 | // 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) 282 | target[method] && target[method].apply(target, args); 283 | // and then continue up the parent node chain if it is still bubbling (if started as bubbles and stopPropagation hasn't been called) 284 | }while(event && event.bubbles && (target = target.parentNode)); 285 | return event && event.cancelable && event; // if it is still true (was cancelable and was cancelled), return the event to indicate default action should happen 286 | }; 287 | var captures = has("event-focusin") ? {} : {focusin: "focus", focusout: "blur"}; 288 | if(!has("event-stopimmediatepropagation")){ 289 | var stopImmediatePropagation =function(){ 290 | this.immediatelyStopped = true; 291 | this.modified = true; // mark it as modified so the event will be cached in IE 292 | }; 293 | var addStopImmediate = function(listener){ 294 | return function(event){ 295 | if(!event.immediatelyStopped){// check to make sure it hasn't been stopped immediately 296 | event.stopImmediatePropagation = stopImmediatePropagation; 297 | return listener.apply(this, arguments); 298 | } 299 | }; 300 | } 301 | } 302 | if(has("dom-addeventlistener")){ 303 | // emitter that works with native event handling 304 | on.emit = function(target, type, event){ 305 | if(target.dispatchEvent && document.createEvent){ 306 | // use the native event emitting mechanism if it is available on the target object 307 | // create a generic event 308 | // we could create branch into the different types of event constructors, but 309 | // that would be a lot of extra code, with little benefit that I can see, seems 310 | // best to use the generic constructor and copy properties over, making it 311 | // easy to have events look like the ones created with specific initializers 312 | var nativeEvent = target.ownerDocument.createEvent("HTMLEvents"); 313 | nativeEvent.initEvent(type, !!event.bubbles, !!event.cancelable); 314 | // and copy all our properties over 315 | for(var i in event){ 316 | if(!(i in nativeEvent)){ 317 | nativeEvent[i] = event[i]; 318 | } 319 | } 320 | return target.dispatchEvent(nativeEvent) && nativeEvent; 321 | } 322 | return syntheticDispatch.apply(on, arguments); // emit for a non-node 323 | }; 324 | }else{ 325 | // no addEventListener, basically old IE event normalization 326 | on._fixEvent = function(evt, sender){ 327 | // summary: 328 | // normalizes properties on the event object including event 329 | // bubbling methods, keystroke normalization, and x/y positions 330 | // evt: 331 | // native event object 332 | // sender: 333 | // node to treat as "currentTarget" 334 | if(!evt){ 335 | var w = sender && (sender.ownerDocument || sender.document || sender).parentWindow || window; 336 | evt = w.event; 337 | } 338 | if(!evt){return evt;} 339 | try{ 340 | if(lastEvent && evt.type == lastEvent.type && evt.srcElement == lastEvent.target){ 341 | // should be same event, reuse event object (so it can be augmented); 342 | // accessing evt.srcElement rather than evt.target since evt.target not set on IE until fixup below 343 | evt = lastEvent; 344 | } 345 | }catch(e){ 346 | // will occur on IE on lastEvent.type reference if lastEvent points to a previous event that already 347 | // finished bubbling, but the setTimeout() to clear lastEvent hasn't fired yet 348 | } 349 | if(!evt.target){ // check to see if it has been fixed yet 350 | evt.target = evt.srcElement; 351 | evt.currentTarget = (sender || evt.srcElement); 352 | if(evt.type == "mouseover"){ 353 | evt.relatedTarget = evt.fromElement; 354 | } 355 | if(evt.type == "mouseout"){ 356 | evt.relatedTarget = evt.toElement; 357 | } 358 | if(!evt.stopPropagation){ 359 | evt.stopPropagation = stopPropagation; 360 | evt.preventDefault = preventDefault; 361 | } 362 | switch(evt.type){ 363 | case "keypress": 364 | var c = ("charCode" in evt ? evt.charCode : evt.keyCode); 365 | if (c==10){ 366 | // CTRL-ENTER is CTRL-ASCII(10) on IE, but CTRL-ENTER on Mozilla 367 | c=0; 368 | evt.keyCode = 13; 369 | }else if(c==13||c==27){ 370 | c=0; // Mozilla considers ENTER and ESC non-printable 371 | }else if(c==3){ 372 | c=99; // Mozilla maps CTRL-BREAK to CTRL-c 373 | } 374 | // Mozilla sets keyCode to 0 when there is a charCode 375 | // but that stops the event on IE. 376 | evt.charCode = c; 377 | _setKeyChar(evt); 378 | break; 379 | } 380 | } 381 | return evt; 382 | }; 383 | var lastEvent, IESignal = function(handle){ 384 | this.handle = handle; 385 | }; 386 | IESignal.prototype.remove = function(){ 387 | delete _dojoIEListeners_[this.handle]; 388 | }; 389 | var fixListener = function(listener){ 390 | // this is a minimal function for closing on the previous listener with as few as variables as possible 391 | return function(evt){ 392 | evt = on._fixEvent(evt, this); 393 | var result = listener.call(this, evt); 394 | if(evt.modified){ 395 | // cache the last event and reuse it if we can 396 | if(!lastEvent){ 397 | setTimeout(function(){ 398 | lastEvent = null; 399 | }); 400 | } 401 | lastEvent = evt; 402 | } 403 | return result; 404 | }; 405 | }; 406 | var fixAttach = function(target, type, listener){ 407 | listener = fixListener(listener); 408 | if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top || 409 | has("jscript") < 5.8) && 410 | !has("config-_allow_leaks")){ 411 | // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below. 412 | // Here we use global redirection to solve the memory leaks 413 | if(typeof _dojoIEListeners_ == "undefined"){ 414 | _dojoIEListeners_ = []; 415 | } 416 | var emitter = target[type]; 417 | if(!emitter || !emitter.listeners){ 418 | var oldListener = emitter; 419 | emitter = Function('event', 'var callee = arguments.callee; for(var i = 0; i