webui-aria2/js/libs/dojo/on.js.uncompressed.js
2012-05-01 19:52:07 +08:00

476 lines
19 KiB
JavaScript

//>>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<callee.listeners.length; i++){var listener = _dojoIEListeners_[callee.listeners[i]]; if(listener){listener.call(this,event);}}');
emiter.listeners = [];
emiter.global = this;
if(oldListener){
emiter.listeners.push(_dojoIEListeners_.push(oldListener) - 1);
}
}
var handle;
emiter.listeners.push(handle = (emiter.global._dojoIEListeners_.push(listener) - 1));
return new IESignal(handle);
}
return aspect.after(target, type, listener, true);
};
var _setKeyChar = function(evt){
evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : '';
evt.charOrCode = evt.keyChar || evt.keyCode;
};
// Called in Event scope
var stopPropagation = function(){
this.cancelBubble = true;
};
var preventDefault = on._preventDefault = function(){
// Setting keyCode to 0 is the only way to prevent certain keypresses (namely
// ctrl-combinations that correspond to menu accelerator keys).
// Otoh, it prevents upstream listeners from getting this information
// Try to split the difference here by clobbering keyCode only for ctrl
// combinations. If you still need to access the key upstream, bubbledKeyCode is
// provided as a workaround.
this.bubbledKeyCode = this.keyCode;
if(this.ctrlKey){
try{
// squelch errors when keyCode is read-only
// (e.g. if keyCode is ctrl or shift)
this.keyCode = 0;
}catch(e){
}
}
this.returnValue = false;
};
}
if(has("touch")){
var Event = function (){};
var windowOrientation = window.orientation;
var fixTouchListener = function(listener){
return function(originalEvent){
//Event normalization(for ontouchxxx and resize):
//1.incorrect e.pageX|pageY in iOS
//2.there are no "e.rotation", "e.scale" and "onorientationchange" in Andriod
//3.More TBD e.g. force | screenX | screenX | clientX | clientY | radiusX | radiusY
// see if it has already been corrected
var event = originalEvent.corrected;
if(!event){
var type = originalEvent.type;
try{
delete originalEvent.type; // on some JS engines (android), deleting properties make them mutable
}catch(e){}
if(originalEvent.type){
// deleting properties doesn't work (older iOS), have to use delegation
Event.prototype = originalEvent;
var event = new Event;
// have to delegate methods to make them work
event.preventDefault = function(){
originalEvent.preventDefault();
};
event.stopPropagation = function(){
originalEvent.stopPropagation();
};
}else{
// deletion worked, use property as is
event = originalEvent;
event.type = type;
}
originalEvent.corrected = event;
if(type == 'resize'){
if(windowOrientation == window.orientation){
return null;//double tap causes an unexpected 'resize' in Andriod
}
windowOrientation = window.orientation;
event.type = "orientationchange";
return listener.call(this, event);
}
// We use the original event and augment, rather than doing an expensive mixin operation
if(!("rotation" in event)){ // test to see if it has rotation
event.rotation = 0;
event.scale = 1;
}
//use event.changedTouches[0].pageX|pageY|screenX|screenY|clientX|clientY|target
var firstChangeTouch = event.changedTouches[0];
for(var i in firstChangeTouch){ // use for-in, we don't need to have dependency on dojo/_base/lang here
delete event[i]; // delete it first to make it mutable
event[i] = firstChangeTouch[i];
}
}
return listener.call(this, event);
};
};
}
return on;
});