//>>built define("dojo/on", ["./has!dom-addeventlistener?:./aspect", "./_base/kernel", "./has"], function(aspect, dojo, has){ // summary: // The export of this module is a function that provides core event listening functionality. With this function // you can provide a target, event type, and listener to be notified of // future matching events that are fired. // target: Element|Object // This is the target object or DOM element that to receive events from // type: String|Function // This is the name of the event to listen for or an extension event type. // listener: Function // This is the function that should be called when the event fires. // returns: Object // An object with a remove() method that can be used to stop listening for this // event. // description: // To listen for "click" events on a button node, we can do: // | define(["dojo/on"], function(listen){ // | on(button, "click", clickHandler); // | ... // Evented JavaScript objects can also have their own events. // | var obj = new Evented; // | on(obj, "foo", fooHandler); // And then we could publish a "foo" event: // | on.emit(obj, "foo", {key: "value"}); // We can use extension events as well. For example, you could listen for a tap gesture: // | define(["dojo/on", "dojo/gesture/tap", function(listen, tap){ // | on(button, tap, tapHandler); // | ... // which would trigger fooHandler. Note that for a simple object this is equivalent to calling: // | obj.onfoo({key:"value"}); // If you use on.emit on a DOM node, it will use native event dispatching when possible. "use strict"; if(1){ // check to make sure we are in a browser, this module should work anywhere var major = window.ScriptEngineMajorVersion; has.add("jscript", major && (major() + ScriptEngineMinorVersion() / 10)); has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this? } var on = function(target, type, listener, dontFix){ if(target.on){ // delegate to the target's on() method, so it can handle it's own listening if it wants return target.on(type, listener); } // delegate to main listener code return on.parse(target, type, listener, addListener, dontFix, this); }; on.pausable = function(target, type, listener, dontFix){ // summary: // This function acts the same as on(), but with pausable functionality. The // returned signal object has pause() and resume() functions. Calling the // pause() method will cause the listener to not be called for future events. Calling the // resume() method will cause the listener to again be called for future events. var paused; var signal = on(target, type, function(){ if(!paused){ return listener.apply(this, arguments); } }, dontFix); signal.pause = function(){ paused = true; }; signal.resume = function(){ paused = false; }; return signal; }; on.once = function(target, type, listener, dontFix){ // summary: // This function acts the same as on(), but will only call the listener once. The // listener will be called for the first // event that takes place and then listener will automatically be removed. var signal = on(target, type, function(){ // remove this listener signal.remove(); // proceed to call the listener return listener.apply(this, arguments); }); return signal; }; on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){ if(type.call){ // event handler function // on(node, dojo.touch.press, touchListener); return type.call(matchesTarget, target, listener); } if(type.indexOf(",") > -1){ // we allow comma delimited event names, so you can register for multiple events at once var events = type.split(/\s*,\s*/); var handles = []; var i = 0; var eventName; while(eventName = events[i++]){ handles.push(addListener(target, eventName, listener, dontFix, matchesTarget)); } handles.remove = function(){ for(var i = 0; i < handles.length; i++){ handles[i].remove(); } }; return handles; } return addListener(target, type, listener, dontFix, matchesTarget) }; var touchEvents = /^touch/; function addListener(target, type, listener, dontFix, matchesTarget){ // event delegation: var selector = type.match(/(.*):(.*)/); // if we have a selector:event, the last one is interpreted as an event, and we use event delegation if(selector){ type = selector[2]; selector = selector[1]; // create the extension event for selectors and directly call it return on.selector(selector, type).call(matchesTarget, target, listener); } // test to see if it a touch event right now, so we don't have to do it every time it fires if(has("touch")){ if(touchEvents.test(type)){ // touch event, fix it listener = fixTouchListener(listener); } if(!has("event-orientationchange") && (type == "orientationchange")){ //"orientationchange" not supported <= Android 2.1, //but works through "resize" on window type = "resize"; target = window; listener = fixTouchListener(listener); } } // normal path, the target is |this| if(target.addEventListener){ // 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) // check for capture conversions var capture = type in captures; target.addEventListener(capture ? captures[type] : type, listener, capture); // create and return the signal return { remove: function(){ target.removeEventListener(type, listener, capture); } }; } type = "on" + type; if(fixAttach && target.attachEvent){ return fixAttach(target, type, listener); } throw new Error("Target must be an event emitter"); } on.selector = function(selector, eventType, children){ // summary: // Creates a new extension event with event delegation. This is based on // the provided event type (can be extension event) that // only calls the listener when the CSS selector matches the target of the event. // selector: // The CSS selector to use for filter events and determine the |this| of the event listener. // eventType: // The event to listen for // children: // Indicates if children elements of the selector should be allowed. This defaults to // true (except in the case of normally non-bubbling events like mouse.enter, in which case it defaults to false). // example: // define(["dojo/on", "dojo/mouse"], function(listen, mouse){ // on(node, on.selector(".my-class", mouse.enter), handlerForMyHover); return function(target, listener){ var matchesTarget = this; var bubble = eventType.bubble; if(bubble){ // the event type doesn't naturally bubble, but has a bubbling form, use that eventType = bubble; }else if(children !== false){ // for normal bubbling events we default to allowing children of the selector children = true; } return on(target, eventType, function(event){ var eventTarget = event.target; // see if we have a valid matchesTarget or default to dojo.query matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query; // there is a selector, so make sure it matches while(!matchesTarget.matches(eventTarget, selector, target)){ if(eventTarget == target || !children || !(eventTarget = eventTarget.parentNode)){ // intentional assignment return; } } return listener.call(eventTarget, event); }); }; }; function syntheticPreventDefault(){ this.cancelable = false; } function syntheticStopPropagation(){ this.bubbles = false; } var slice = [].slice, syntheticDispatch = on.emit = function(target, type, event){ // summary: // Fires an event on the target object. // target: // The target object to fire the event on. This can be a DOM element or a plain // JS object. If the target is a DOM element, native event emiting mechanisms // are used when possible. // type: // The event type name. You can emulate standard native events like "click" and // "mouseover" or create custom events like "open" or "finish". // event: // An object that provides the properties for the event. See https://developer.mozilla.org/en/DOM/event.initEvent // for some of the properties. These properties are copied to the event object. // Of particular importance are the cancelable and bubbles properties. The // cancelable property indicates whether or not the event has a default action // that can be cancelled. The event is cancelled by calling preventDefault() on // the event object. The bubbles property indicates whether or not the // event will bubble up the DOM tree. If bubbles is true, the event will be called // on the target and then each parent successively until the top of the tree // is reached or stopPropagation() is called. Both bubbles and cancelable // default to false. // returns: // If the event is cancelable and the event is not cancelled, // emit will return true. If the event is cancelable and the event is cancelled, // emit will return false. // details: // Note that this is designed to emit events for listeners registered through // dojo/on. It should actually work with any event listener except those // added through IE's attachEvent (IE8 and below's non-W3C event emiting // doesn't support custom event types). It should work with all events registered // through dojo/on. Also note that the emit method does do any default // action, it only returns a value to indicate if the default action should take // place. For example, emiting a keypress event would not cause a character // to appear in a textbox. // example: // To fire our own click event // | on.emit(dojo.byId("button"), "click", { // | cancelable: true, // | bubbles: true, // | screenX: 33, // | screenY: 44 // | }); // We can also fire our own custom events: // | on.emit(dojo.byId("slider"), "slide", { // | cancelable: true, // | bubbles: true, // | direction: "left-to-right" // | }); var args = slice.call(arguments, 2); var method = "on" + type; if("parentNode" in target){ // node (or node-like), create event controller methods var newEvent = args[0] = {}; for(var i in event){ newEvent[i] = event[i]; } newEvent.preventDefault = syntheticPreventDefault; newEvent.stopPropagation = syntheticStopPropagation; newEvent.target = target; newEvent.type = type; event = newEvent; } do{ // 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) target[method] && target[method].apply(target, args); // and then continue up the parent node chain if it is still bubbling (if started as bubbles and stopPropagation hasn't been called) }while(event && event.bubbles && (target = target.parentNode)); return event && event.cancelable && event; // if it is still true (was cancelable and was cancelled), return the event to indicate default action should happen }; var captures = {}; if(has("dom-addeventlistener")){ // normalize focusin and focusout captures = { focusin: "focus", focusout: "blur" }; if(has("opera")){ captures.keydown = "keypress"; // this one needs to be transformed because Opera doesn't support repeating keys on keydown (and keypress works because it incorrectly fires on all keydown events) } // emiter that works with native event handling on.emit = function(target, type, event){ if(target.dispatchEvent && document.createEvent){ // use the native event emiting mechanism if it is available on the target object // create a generic event // we could create branch into the different types of event constructors, but // that would be a lot of extra code, with little benefit that I can see, seems // best to use the generic constructor and copy properties over, making it // easy to have events look like the ones created with specific initializers var nativeEvent = document.createEvent("HTMLEvents"); nativeEvent.initEvent(type, !!event.bubbles, !!event.cancelable); // and copy all our properties over for(var i in event){ var value = event[i]; if(!(i in nativeEvent)){ nativeEvent[i] = event[i]; } } return target.dispatchEvent(nativeEvent) && nativeEvent; } return syntheticDispatch.apply(on, arguments); // emit for a non-node }; }else{ // no addEventListener, basically old IE event normalization on._fixEvent = function(evt, sender){ // summary: // normalizes properties on the event object including event // bubbling methods, keystroke normalization, and x/y positions // evt: // native event object // sender: // node to treat as "currentTarget" if(!evt){ var w = sender && (sender.ownerDocument || sender.document || sender).parentWindow || window; evt = w.event; } if(!evt){return(evt);} if(!evt.target){ // check to see if it has been fixed yet evt.target = evt.srcElement; evt.currentTarget = (sender || evt.srcElement); if(evt.type == "mouseover"){ evt.relatedTarget = evt.fromElement; } if(evt.type == "mouseout"){ evt.relatedTarget = evt.toElement; } if(!evt.stopPropagation){ evt.stopPropagation = stopPropagation; evt.preventDefault = preventDefault; } switch(evt.type){ case "keypress": var c = ("charCode" in evt ? evt.charCode : evt.keyCode); if (c==10){ // CTRL-ENTER is CTRL-ASCII(10) on IE, but CTRL-ENTER on Mozilla c=0; evt.keyCode = 13; }else if(c==13||c==27){ c=0; // Mozilla considers ENTER and ESC non-printable }else if(c==3){ c=99; // Mozilla maps CTRL-BREAK to CTRL-c } // Mozilla sets keyCode to 0 when there is a charCode // but that stops the event on IE. evt.charCode = c; _setKeyChar(evt); break; } } return evt; }; var IESignal = function(handle){ this.handle = handle; }; IESignal.prototype.remove = function(){ delete _dojoIEListeners_[this.handle]; }; var fixListener = function(listener){ // this is a minimal function for closing on the previous listener with as few as variables as possible return function(evt){ evt = on._fixEvent(evt, this); return listener.call(this, evt); } } var fixAttach = function(target, type, listener){ listener = fixListener(listener); if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top || has("jscript") < 5.8) && !has("config-_allow_leaks")){ // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below. // Here we use global redirection to solve the memory leaks if(typeof _dojoIEListeners_ == "undefined"){ _dojoIEListeners_ = []; } var emiter = target[type]; if(!emiter || !emiter.listeners){ var oldListener = emiter; target[type] = emiter = Function('event', 'var callee = arguments.callee; for(var i = 0; i