blob: 325cbf3dd7783de91cd1af8626cdf8cd44347aec [file] [log] [blame]
/*
Copyright (c) 2004-2005, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.require("dojo.lang");
dojo.provide("dojo.event");
dojo.event = new function(){
this.canTimeout = dojo.lang.isFunction(dj_global["setTimeout"])||dojo.lang.isAlien(dj_global["setTimeout"]);
// FIXME: where should we put this method (not here!)?
function interpolateArgs(args){
var dl = dojo.lang;
var ao = {
srcObj: dj_global,
srcFunc: null,
adviceObj: dj_global,
adviceFunc: null,
aroundObj: null,
aroundFunc: null,
adviceType: (args.length>2) ? args[0] : "after",
precedence: "last",
once: false,
delay: null,
rate: 0,
adviceMsg: false
};
switch(args.length){
case 0: return;
case 1: return;
case 2:
ao.srcFunc = args[0];
ao.adviceFunc = args[1];
break;
case 3:
if((dl.isObject(args[0]))&&(dl.isString(args[1]))&&(dl.isString(args[2]))){
ao.adviceType = "after";
ao.srcObj = args[0];
ao.srcFunc = args[1];
ao.adviceFunc = args[2];
}else if((dl.isString(args[1]))&&(dl.isString(args[2]))){
ao.srcFunc = args[1];
ao.adviceFunc = args[2];
}else if((dl.isObject(args[0]))&&(dl.isString(args[1]))&&(dl.isFunction(args[2]))){
ao.adviceType = "after";
ao.srcObj = args[0];
ao.srcFunc = args[1];
var tmpName = dojo.lang.nameAnonFunc(args[2], ao.adviceObj);
ao.adviceFunc = tmpName;
}else if((dl.isFunction(args[0]))&&(dl.isObject(args[1]))&&(dl.isString(args[2]))){
ao.adviceType = "after";
ao.srcObj = dj_global;
var tmpName = dojo.lang.nameAnonFunc(args[0], ao.srcObj);
ao.srcFunc = tmpName;
ao.adviceObj = args[1];
ao.adviceFunc = args[2];
}
break;
case 4:
if((dl.isObject(args[0]))&&(dl.isObject(args[2]))){
// we can assume that we've got an old-style "connect" from
// the sigslot school of event attachment. We therefore
// assume after-advice.
ao.adviceType = "after";
ao.srcObj = args[0];
ao.srcFunc = args[1];
ao.adviceObj = args[2];
ao.adviceFunc = args[3];
}else if((dl.isString(args[0]))&&(dl.isString(args[1]))&&(dl.isObject(args[2]))){
ao.adviceType = args[0];
ao.srcObj = dj_global;
ao.srcFunc = args[1];
ao.adviceObj = args[2];
ao.adviceFunc = args[3];
}else if((dl.isString(args[0]))&&(dl.isFunction(args[1]))&&(dl.isObject(args[2]))){
ao.adviceType = args[0];
ao.srcObj = dj_global;
var tmpName = dojo.lang.nameAnonFunc(args[1], dj_global);
ao.srcFunc = tmpName;
ao.adviceObj = args[2];
ao.adviceFunc = args[3];
}else if(dl.isObject(args[1])){
ao.srcObj = args[1];
ao.srcFunc = args[2];
ao.adviceObj = dj_global;
ao.adviceFunc = args[3];
}else if(dl.isObject(args[2])){
ao.srcObj = dj_global;
ao.srcFunc = args[1];
ao.adviceObj = args[2];
ao.adviceFunc = args[3];
}else{
ao.srcObj = ao.adviceObj = ao.aroundObj = dj_global;
ao.srcFunc = args[1];
ao.adviceFunc = args[2];
ao.aroundFunc = args[3];
}
break;
case 6:
ao.srcObj = args[1];
ao.srcFunc = args[2];
ao.adviceObj = args[3]
ao.adviceFunc = args[4];
ao.aroundFunc = args[5];
ao.aroundObj = dj_global;
break;
default:
ao.srcObj = args[1];
ao.srcFunc = args[2];
ao.adviceObj = args[3]
ao.adviceFunc = args[4];
ao.aroundObj = args[5];
ao.aroundFunc = args[6];
ao.once = args[7];
ao.delay = args[8];
ao.rate = args[9];
ao.adviceMsg = args[10];
break;
}
if((typeof ao.srcFunc).toLowerCase() != "string"){
ao.srcFunc = dojo.lang.getNameInObj(ao.srcObj, ao.srcFunc);
}
if((typeof ao.adviceFunc).toLowerCase() != "string"){
ao.adviceFunc = dojo.lang.getNameInObj(ao.adviceObj, ao.adviceFunc);
}
if((ao.aroundObj)&&((typeof ao.aroundFunc).toLowerCase() != "string")){
ao.aroundFunc = dojo.lang.getNameInObj(ao.aroundObj, ao.aroundFunc);
}
if(!ao.srcObj){
dojo.raise("bad srcObj for srcFunc: "+ao.srcFunc);
}
if(!ao.adviceObj){
dojo.raise("bad adviceObj for adviceFunc: "+ao.adviceFunc);
}
return ao;
}
this.connect = function(){
var ao = interpolateArgs(arguments);
// FIXME: just doing a "getForMethod()" seems to be enough to put this into infinite recursion!!
var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc);
if(ao.adviceFunc){
var mjp2 = dojo.event.MethodJoinPoint.getForMethod(ao.adviceObj, ao.adviceFunc);
}
mjp.kwAddAdvice(ao);
return mjp; // advanced users might want to fsck w/ the join point
// manually
}
this.connectBefore = function() {
var args = ["before"];
for(var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
return this.connect.apply(this, args);
}
this.connectAround = function() {
var args = ["around"];
for(var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
return this.connect.apply(this, args);
}
this._kwConnectImpl = function(kwArgs, disconnect){
var fn = (disconnect) ? "disconnect" : "connect";
if(typeof kwArgs["srcFunc"] == "function"){
kwArgs.srcObj = kwArgs["srcObj"]||dj_global;
var tmpName = dojo.lang.nameAnonFunc(kwArgs.srcFunc, kwArgs.srcObj);
kwArgs.srcFunc = tmpName;
}
if(typeof kwArgs["adviceFunc"] == "function"){
kwArgs.adviceObj = kwArgs["adviceObj"]||dj_global;
var tmpName = dojo.lang.nameAnonFunc(kwArgs.adviceFunc, kwArgs.adviceObj);
kwArgs.adviceFunc = tmpName;
}
return dojo.event[fn]( (kwArgs["type"]||kwArgs["adviceType"]||"after"),
kwArgs["srcObj"]||dj_global,
kwArgs["srcFunc"],
kwArgs["adviceObj"]||kwArgs["targetObj"]||dj_global,
kwArgs["adviceFunc"]||kwArgs["targetFunc"],
kwArgs["aroundObj"],
kwArgs["aroundFunc"],
kwArgs["once"],
kwArgs["delay"],
kwArgs["rate"],
kwArgs["adviceMsg"]||false );
}
this.kwConnect = function(kwArgs){
return this._kwConnectImpl(kwArgs, false);
}
this.disconnect = function(){
var ao = interpolateArgs(arguments);
if(!ao.adviceFunc){ return; } // nothing to disconnect
var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc);
return mjp.removeAdvice(ao.adviceObj, ao.adviceFunc, ao.adviceType, ao.once);
}
this.kwDisconnect = function(kwArgs){
return this._kwConnectImpl(kwArgs, true);
}
}
// exactly one of these is created whenever a method with a joint point is run,
// if there is at least one 'around' advice.
dojo.event.MethodInvocation = function(join_point, obj, args) {
this.jp_ = join_point;
this.object = obj;
this.args = [];
for(var x=0; x<args.length; x++){
this.args[x] = args[x];
}
// the index of the 'around' that is currently being executed.
this.around_index = -1;
}
dojo.event.MethodInvocation.prototype.proceed = function() {
this.around_index++;
if(this.around_index >= this.jp_.around.length){
return this.jp_.object[this.jp_.methodname].apply(this.jp_.object, this.args);
// return this.jp_.run_before_after(this.object, this.args);
}else{
var ti = this.jp_.around[this.around_index];
var mobj = ti[0]||dj_global;
var meth = ti[1];
return mobj[meth].call(mobj, this);
}
}
dojo.event.MethodJoinPoint = function(obj, methname){
this.object = obj||dj_global;
this.methodname = methname;
this.methodfunc = this.object[methname];
this.before = [];
this.after = [];
this.around = [];
}
dojo.event.MethodJoinPoint.getForMethod = function(obj, methname) {
// if(!(methname in obj)){
if(!obj){ obj = dj_global; }
if(!obj[methname]){
// supply a do-nothing method implementation
obj[methname] = function(){};
}else if((!dojo.lang.isFunction(obj[methname]))&&(!dojo.lang.isAlien(obj[methname]))){
return null; // FIXME: should we throw an exception here instead?
}
// we hide our joinpoint instance in obj[methname + '$joinpoint']
var jpname = methname + "$joinpoint";
var jpfuncname = methname + "$joinpoint$method";
var joinpoint = obj[jpname];
if(!joinpoint){
var isNode = false;
if(dojo.event["browser"]){
if( (obj["attachEvent"])||
(obj["nodeType"])||
(obj["addEventListener"]) ){
isNode = true;
dojo.event.browser.addClobberNodeAttrs(obj, [jpname, jpfuncname, methname]);
}
}
obj[jpfuncname] = obj[methname];
// joinpoint = obj[jpname] = new dojo.event.MethodJoinPoint(obj, methname);
joinpoint = obj[jpname] = new dojo.event.MethodJoinPoint(obj, jpfuncname);
obj[methname] = function(){
var args = [];
if((isNode)&&(!arguments.length)&&(window.event)){
args.push(dojo.event.browser.fixEvent(window.event));
}else{
for(var x=0; x<arguments.length; x++){
if((x==0)&&(isNode)&&(dojo.event.browser.isEvent(arguments[x]))){
args.push(dojo.event.browser.fixEvent(arguments[x]));
}else{
args.push(arguments[x]);
}
}
}
// return joinpoint.run.apply(joinpoint, arguments);
return joinpoint.run.apply(joinpoint, args);
}
}
return joinpoint;
}
dojo.lang.extend(dojo.event.MethodJoinPoint, {
unintercept: function() {
this.object[this.methodname] = this.methodfunc;
},
run: function() {
var obj = this.object||dj_global;
var args = arguments;
// optimization. We only compute once the array version of the arguments
// pseudo-arr in order to prevent building it each time advice is unrolled.
var aargs = [];
for(var x=0; x<args.length; x++){
aargs[x] = args[x];
}
var unrollAdvice = function(marr){
if(!marr){
dojo.debug("Null argument to unrollAdvice()");
return;
}
var callObj = marr[0]||dj_global;
var callFunc = marr[1];
if(!callObj[callFunc]){
dojo.raise("function \"" + callFunc + "\" does not exist on \"" + callObj + "\"");
}
var aroundObj = marr[2]||dj_global;
var aroundFunc = marr[3];
var msg = marr[6];
var undef;
var to = {
args: [],
jp_: this,
object: obj,
proceed: function(){
return callObj[callFunc].apply(callObj, to.args);
}
};
to.args = aargs;
var delay = parseInt(marr[4]);
var hasDelay = ((!isNaN(delay))&&(marr[4]!==null)&&(typeof marr[4] != "undefined"));
if(marr[5]){
var rate = parseInt(marr[5]);
var cur = new Date();
var timerSet = false;
if((marr["last"])&&((cur-marr.last)<=rate)){
if(dojo.event.canTimeout){
if(marr["delayTimer"]){
clearTimeout(marr.delayTimer);
}
var tod = parseInt(rate*2); // is rate*2 naive?
var mcpy = dojo.lang.shallowCopy(marr);
marr.delayTimer = setTimeout(function(){
// FIXME: on IE at least, event objects from the
// browser can go out of scope. How (or should?) we
// deal with it?
mcpy[5] = 0;
unrollAdvice(mcpy);
}, tod);
}
return;
}else{
marr.last = cur;
}
}
// FIXME: need to enforce rates for a connection here!
if(aroundFunc){
// NOTE: around advice can't delay since we might otherwise depend
// on execution order!
aroundObj[aroundFunc].call(aroundObj, to);
}else{
// var tmjp = dojo.event.MethodJoinPoint.getForMethod(obj, methname);
if((hasDelay)&&((dojo.render.html)||(dojo.render.svg))){ // FIXME: the render checks are grotty!
dj_global["setTimeout"](function(){
if(msg){
callObj[callFunc].call(callObj, to);
}else{
callObj[callFunc].apply(callObj, args);
}
}, delay);
}else{ // many environments can't support delay!
if(msg){
callObj[callFunc].call(callObj, to);
}else{
callObj[callFunc].apply(callObj, args);
}
}
}
}
if(this.before.length>0){
dojo.lang.forEach(this.before, unrollAdvice, true);
}
var result;
if(this.around.length>0){
var mi = new dojo.event.MethodInvocation(this, obj, args);
result = mi.proceed();
}else if(this.methodfunc){
result = this.object[this.methodname].apply(this.object, args);
}
if(this.after.length>0){
dojo.lang.forEach(this.after, unrollAdvice, true);
}
return (this.methodfunc) ? result : null;
},
getArr: function(kind){
var arr = this.after;
// FIXME: we should be able to do this through props or Array.in()
if((typeof kind == "string")&&(kind.indexOf("before")!=-1)){
arr = this.before;
}else if(kind=="around"){
arr = this.around;
}
return arr;
},
kwAddAdvice: function(args){
this.addAdvice( args["adviceObj"], args["adviceFunc"],
args["aroundObj"], args["aroundFunc"],
args["adviceType"], args["precedence"],
args["once"], args["delay"], args["rate"],
args["adviceMsg"]);
},
addAdvice: function( thisAdviceObj, thisAdvice,
thisAroundObj, thisAround,
advice_kind, precedence,
once, delay, rate, asMessage){
var arr = this.getArr(advice_kind);
if(!arr){
dojo.raise("bad this: " + this);
}
var ao = [thisAdviceObj, thisAdvice, thisAroundObj, thisAround, delay, rate, asMessage];
if(once){
if(this.hasAdvice(thisAdviceObj, thisAdvice, advice_kind, arr) >= 0){
return;
}
}
if(precedence == "first"){
arr.unshift(ao);
}else{
arr.push(ao);
}
},
hasAdvice: function(thisAdviceObj, thisAdvice, advice_kind, arr){
if(!arr){ arr = this.getArr(advice_kind); }
var ind = -1;
for(var x=0; x<arr.length; x++){
if((arr[x][0] == thisAdviceObj)&&(arr[x][1] == thisAdvice)){
ind = x;
}
}
return ind;
},
removeAdvice: function(thisAdviceObj, thisAdvice, advice_kind, once){
var arr = this.getArr(advice_kind);
var ind = this.hasAdvice(thisAdviceObj, thisAdvice, advice_kind, arr);
if(ind == -1){
return false;
}
while(ind != -1){
arr.splice(ind, 1);
if(once){ break; }
ind = this.hasAdvice(thisAdviceObj, thisAdvice, advice_kind, arr);
}
return true;
}
});