blob: d9ec139d031ab2d046f175fc50117bc58631708d [file] [log] [blame]
/*
Copyright (c) 2004-2006, 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.provide("dojo.event.browser");
dojo.require("dojo.event.common");
// FIXME: any particular reason this is in the global scope?
dojo._ie_clobber = new function(){
this.clobberNodes = [];
function nukeProp(node, prop){
// try{ node.removeAttribute(prop); }catch(e){ /* squelch */ }
try{ node[prop] = null; }catch(e){ /* squelch */ }
try{ delete node[prop]; }catch(e){ /* squelch */ }
// FIXME: JotLive needs this, but I'm not sure if it's too slow or not
try{ node.removeAttribute(prop); }catch(e){ /* squelch */ }
}
this.clobber = function(nodeRef){
var na;
var tna;
if(nodeRef){
tna = nodeRef.all || nodeRef.getElementsByTagName("*");
na = [nodeRef];
for(var x=0; x<tna.length; x++){
// if we're gonna be clobbering the thing, at least make sure
// we aren't trying to do it twice
if(tna[x]["__doClobber__"]){
na.push(tna[x]);
}
}
}else{
try{ window.onload = null; }catch(e){}
na = (this.clobberNodes.length) ? this.clobberNodes : document.all;
}
tna = null;
var basis = {};
for(var i = na.length-1; i>=0; i=i-1){
var el = na[i];
try{
if(el && el["__clobberAttrs__"]){
for(var j=0; j<el.__clobberAttrs__.length; j++){
nukeProp(el, el.__clobberAttrs__[j]);
}
nukeProp(el, "__clobberAttrs__");
nukeProp(el, "__doClobber__");
}
}catch(e){ /* squelch! */};
}
na = null;
}
}
if(dojo.render.html.ie){
dojo.addOnUnload(function(){
dojo._ie_clobber.clobber();
try{
if((dojo["widget"])&&(dojo.widget["manager"])){
dojo.widget.manager.destroyAll();
}
}catch(e){}
// Workaround for IE leak recommended in ticket #1727 by schallm
if(dojo.widget){
for(var name in dojo.widget._templateCache){
if(dojo.widget._templateCache[name].node){
dojo.dom.destroyNode(dojo.widget._templateCache[name].node);
dojo.widget._templateCache[name].node = null;
delete dojo.widget._templateCache[name].node;
}
}
}
try{ window.onload = null; }catch(e){}
try{ window.onunload = null; }catch(e){}
dojo._ie_clobber.clobberNodes = [];
// CollectGarbage();
});
}
dojo.event.browser = new function(){
var clobberIdx = 0;
this.normalizedEventName = function(/*String*/eventName){
switch(eventName){
case "CheckboxStateChange":
case "DOMAttrModified":
case "DOMMenuItemActive":
case "DOMMenuItemInactive":
case "DOMMouseScroll":
case "DOMNodeInserted":
case "DOMNodeRemoved":
case "RadioStateChange":
return eventName;
break;
default:
return eventName.toLowerCase();
break;
}
}
this.clean = function(/*DOMNode*/node){
// summary:
// removes native event handlers so that destruction of the node
// will not leak memory. On most browsers this is a no-op, but
// it's critical for manual node removal on IE.
// node:
// A DOM node. All of it's children will also be cleaned.
if(dojo.render.html.ie){
dojo._ie_clobber.clobber(node);
}
}
this.addClobberNode = function(/*DOMNode*/node){
// summary:
// register the passed node to support event stripping
// node:
// A DOM node
if(!dojo.render.html.ie){ return; }
if(!node["__doClobber__"]){
node.__doClobber__ = true;
dojo._ie_clobber.clobberNodes.push(node);
// this might not be the most efficient thing to do, but it's
// much less error prone than other approaches which were
// previously tried and failed
node.__clobberAttrs__ = [];
}
}
this.addClobberNodeAttrs = function(/*DOMNode*/node, /*Array*/props){
// summary:
// register the passed node to support event stripping
// node:
// A DOM node to stip properties from later
// props:
// A list of propeties to strip from the node
if(!dojo.render.html.ie){ return; }
this.addClobberNode(node);
for(var x=0; x<props.length; x++){
node.__clobberAttrs__.push(props[x]);
}
}
this.removeListener = function( /*DOMNode*/ node,
/*String*/ evtName,
/*Function*/fp,
/*Boolean*/ capture){
// summary:
// clobbers the listener from the node
// evtName:
// the name of the handler to remove the function from
// node:
// DOM node to attach the event to
// fp:
// the function to register
// capture:
// Optional. should this listener prevent propigation?
if(!capture){ var capture = false; }
evtName = dojo.event.browser.normalizedEventName(evtName);
if( (evtName == "onkey") || (evtName == "key") ){
if(dojo.render.html.ie){
this.removeListener(node, "onkeydown", fp, capture);
}
evtName = "onkeypress";
}
if(evtName.substr(0,2)=="on"){ evtName = evtName.substr(2); }
// FIXME: this is mostly a punt, we aren't actually doing anything on IE
if(node.removeEventListener){
node.removeEventListener(evtName, fp, capture);
}
}
this.addListener = function(/*DOMNode*/node, /*String*/evtName, /*Function*/fp, /*Boolean*/capture, /*Boolean*/dontFix){
// summary:
// adds a listener to the node
// evtName:
// the name of the handler to add the listener to can be either of
// the form "onclick" or "click"
// node:
// DOM node to attach the event to
// fp:
// the function to register
// capture:
// Optional. Should this listener prevent propigation?
// dontFix:
// Optional. Should we avoid registering a new closure around the
// listener to enable fixEvent for dispatch of the registered
// function?
if(!node){ return; } // FIXME: log and/or bail?
if(!capture){ var capture = false; }
evtName = dojo.event.browser.normalizedEventName(evtName);
if( (evtName == "onkey") || (evtName == "key") ){
if(dojo.render.html.ie){
this.addListener(node, "onkeydown", fp, capture, dontFix);
}
evtName = "onkeypress";
}
if(evtName.substr(0,2)!="on"){ evtName = "on"+evtName; }
if(!dontFix){
// build yet another closure around fp in order to inject fixEvent
// around the resulting event
var newfp = function(evt){
if(!evt){ evt = window.event; }
var ret = fp(dojo.event.browser.fixEvent(evt, this));
if(capture){
dojo.event.browser.stopEvent(evt);
}
return ret;
}
}else{
newfp = fp;
}
if(node.addEventListener){
node.addEventListener(evtName.substr(2), newfp, capture);
return newfp;
}else{
if(typeof node[evtName] == "function" ){
var oldEvt = node[evtName];
node[evtName] = function(e){
oldEvt(e);
return newfp(e);
}
}else{
node[evtName]=newfp;
}
if(dojo.render.html.ie){
this.addClobberNodeAttrs(node, [evtName]);
}
return newfp;
}
}
this.isEvent = function(/*Object*/obj){
// summary:
// Tries to determine whether or not the object is a DOM event.
// FIXME: event detection hack ... could test for additional attributes
// if necessary
return (typeof obj != "undefined")&&(obj)&&(typeof Event != "undefined")&&(obj.eventPhase); // Boolean
// Event does not support instanceof in Opera, otherwise:
//return (typeof Event != "undefined")&&(obj instanceof Event);
}
this.currentEvent = null;
this.callListener = function(/*Function*/listener, /*DOMNode*/curTarget){
// summary:
// calls the specified listener in the context of the passed node
// with the current DOM event object as the only parameter
// listener:
// the function to call
// curTarget:
// the Node to call the function in the scope of
if(typeof listener != 'function'){
dojo.raise("listener not a function: " + listener);
}
dojo.event.browser.currentEvent.currentTarget = curTarget;
return listener.call(curTarget, dojo.event.browser.currentEvent);
}
this._stopPropagation = function(){
dojo.event.browser.currentEvent.cancelBubble = true;
}
this._preventDefault = function(){
dojo.event.browser.currentEvent.returnValue = false;
}
this.keys = {
KEY_BACKSPACE: 8,
KEY_TAB: 9,
KEY_CLEAR: 12,
KEY_ENTER: 13,
KEY_SHIFT: 16,
KEY_CTRL: 17,
KEY_ALT: 18,
KEY_PAUSE: 19,
KEY_CAPS_LOCK: 20,
KEY_ESCAPE: 27,
KEY_SPACE: 32,
KEY_PAGE_UP: 33,
KEY_PAGE_DOWN: 34,
KEY_END: 35,
KEY_HOME: 36,
KEY_LEFT_ARROW: 37,
KEY_UP_ARROW: 38,
KEY_RIGHT_ARROW: 39,
KEY_DOWN_ARROW: 40,
KEY_INSERT: 45,
KEY_DELETE: 46,
KEY_HELP: 47,
KEY_LEFT_WINDOW: 91,
KEY_RIGHT_WINDOW: 92,
KEY_SELECT: 93,
KEY_NUMPAD_0: 96,
KEY_NUMPAD_1: 97,
KEY_NUMPAD_2: 98,
KEY_NUMPAD_3: 99,
KEY_NUMPAD_4: 100,
KEY_NUMPAD_5: 101,
KEY_NUMPAD_6: 102,
KEY_NUMPAD_7: 103,
KEY_NUMPAD_8: 104,
KEY_NUMPAD_9: 105,
KEY_NUMPAD_MULTIPLY: 106,
KEY_NUMPAD_PLUS: 107,
KEY_NUMPAD_ENTER: 108,
KEY_NUMPAD_MINUS: 109,
KEY_NUMPAD_PERIOD: 110,
KEY_NUMPAD_DIVIDE: 111,
KEY_F1: 112,
KEY_F2: 113,
KEY_F3: 114,
KEY_F4: 115,
KEY_F5: 116,
KEY_F6: 117,
KEY_F7: 118,
KEY_F8: 119,
KEY_F9: 120,
KEY_F10: 121,
KEY_F11: 122,
KEY_F12: 123,
KEY_F13: 124,
KEY_F14: 125,
KEY_F15: 126,
KEY_NUM_LOCK: 144,
KEY_SCROLL_LOCK: 145
};
// reverse lookup
this.revKeys = [];
for(var key in this.keys){
this.revKeys[this.keys[key]] = key;
}
this.fixEvent = function(/*Event*/evt, /*DOMNode*/sender){
// summary:
// normalizes properties on the event object including event
// bubbling methods, keystroke normalization, and x/y positions
// evt: the native event object
// sender: the node to treat as "currentTarget"
if(!evt){
if(window["event"]){
evt = window.event;
}
}
if((evt["type"])&&(evt["type"].indexOf("key") == 0)){ // key events
evt.keys = this.revKeys;
// FIXME: how can we eliminate this iteration?
for(var key in this.keys){
evt[key] = this.keys[key];
}
if(evt["type"] == "keydown" && dojo.render.html.ie){
switch(evt.keyCode){
case evt.KEY_SHIFT:
case evt.KEY_CTRL:
case evt.KEY_ALT:
case evt.KEY_CAPS_LOCK:
case evt.KEY_LEFT_WINDOW:
case evt.KEY_RIGHT_WINDOW:
case evt.KEY_SELECT:
case evt.KEY_NUM_LOCK:
case evt.KEY_SCROLL_LOCK:
// I'll get these in keypress after the OS munges them based on numlock
case evt.KEY_NUMPAD_0:
case evt.KEY_NUMPAD_1:
case evt.KEY_NUMPAD_2:
case evt.KEY_NUMPAD_3:
case evt.KEY_NUMPAD_4:
case evt.KEY_NUMPAD_5:
case evt.KEY_NUMPAD_6:
case evt.KEY_NUMPAD_7:
case evt.KEY_NUMPAD_8:
case evt.KEY_NUMPAD_9:
case evt.KEY_NUMPAD_PERIOD:
break; // just ignore the keys that can morph
case evt.KEY_NUMPAD_MULTIPLY:
case evt.KEY_NUMPAD_PLUS:
case evt.KEY_NUMPAD_ENTER:
case evt.KEY_NUMPAD_MINUS:
case evt.KEY_NUMPAD_DIVIDE:
break; // I could handle these but just pick them up in keypress
case evt.KEY_PAUSE:
case evt.KEY_TAB:
case evt.KEY_BACKSPACE:
case evt.KEY_ENTER:
case evt.KEY_ESCAPE:
case evt.KEY_PAGE_UP:
case evt.KEY_PAGE_DOWN:
case evt.KEY_END:
case evt.KEY_HOME:
case evt.KEY_LEFT_ARROW:
case evt.KEY_UP_ARROW:
case evt.KEY_RIGHT_ARROW:
case evt.KEY_DOWN_ARROW:
case evt.KEY_INSERT:
case evt.KEY_DELETE:
case evt.KEY_F1:
case evt.KEY_F2:
case evt.KEY_F3:
case evt.KEY_F4:
case evt.KEY_F5:
case evt.KEY_F6:
case evt.KEY_F7:
case evt.KEY_F8:
case evt.KEY_F9:
case evt.KEY_F10:
case evt.KEY_F11:
case evt.KEY_F12:
case evt.KEY_F12:
case evt.KEY_F13:
case evt.KEY_F14:
case evt.KEY_F15:
case evt.KEY_CLEAR:
case evt.KEY_HELP:
evt.key = evt.keyCode;
break;
default:
if(evt.ctrlKey || evt.altKey){
var unifiedCharCode = evt.keyCode;
// if lower case but keycode is uppercase, convert it
if(unifiedCharCode >= 65 && unifiedCharCode <= 90 && evt.shiftKey == false){
unifiedCharCode += 32;
}
if(unifiedCharCode >= 1 && unifiedCharCode <= 26 && evt.ctrlKey){
unifiedCharCode += 96; // 001-032 = ctrl+[a-z]
}
evt.key = String.fromCharCode(unifiedCharCode);
}
}
} else if(evt["type"] == "keypress"){
if(dojo.render.html.opera){
if(evt.which == 0){
evt.key = evt.keyCode;
}else if(evt.which > 0){
switch(evt.which){
case evt.KEY_SHIFT:
case evt.KEY_CTRL:
case evt.KEY_ALT:
case evt.KEY_CAPS_LOCK:
case evt.KEY_NUM_LOCK:
case evt.KEY_SCROLL_LOCK:
break;
case evt.KEY_PAUSE:
case evt.KEY_TAB:
case evt.KEY_BACKSPACE:
case evt.KEY_ENTER:
case evt.KEY_ESCAPE:
evt.key = evt.which;
break;
default:
var unifiedCharCode = evt.which;
if((evt.ctrlKey || evt.altKey || evt.metaKey) && (evt.which >= 65 && evt.which <= 90 && evt.shiftKey == false)){
unifiedCharCode += 32;
}
evt.key = String.fromCharCode(unifiedCharCode);
}
}
}else if(dojo.render.html.ie){ // catch some IE keys that are hard to get in keyDown
// key combinations were handled in onKeyDown
if(!evt.ctrlKey && !evt.altKey && evt.keyCode >= evt.KEY_SPACE){
evt.key = String.fromCharCode(evt.keyCode);
}
}else if(dojo.render.html.safari){
switch(evt.keyCode){
case 25: evt.key = evt.KEY_TAB; evt.shift = true;break;
case 63232: evt.key = evt.KEY_UP_ARROW; break;
case 63233: evt.key = evt.KEY_DOWN_ARROW; break;
case 63234: evt.key = evt.KEY_LEFT_ARROW; break;
case 63235: evt.key = evt.KEY_RIGHT_ARROW; break;
case 63236: evt.key = evt.KEY_F1; break;
case 63237: evt.key = evt.KEY_F2; break;
case 63238: evt.key = evt.KEY_F3; break;
case 63239: evt.key = evt.KEY_F4; break;
case 63240: evt.key = evt.KEY_F5; break;
case 63241: evt.key = evt.KEY_F6; break;
case 63242: evt.key = evt.KEY_F7; break;
case 63243: evt.key = evt.KEY_F8; break;
case 63244: evt.key = evt.KEY_F9; break;
case 63245: evt.key = evt.KEY_F10; break;
case 63246: evt.key = evt.KEY_F11; break;
case 63247: evt.key = evt.KEY_F12; break;
case 63250: evt.key = evt.KEY_PAUSE; break;
case 63272: evt.key = evt.KEY_DELETE; break;
case 63273: evt.key = evt.KEY_HOME; break;
case 63275: evt.key = evt.KEY_END; break;
case 63276: evt.key = evt.KEY_PAGE_UP; break;
case 63277: evt.key = evt.KEY_PAGE_DOWN; break;
case 63302: evt.key = evt.KEY_INSERT; break;
case 63248://prtscr
case 63249://scrolllock
case 63289://numlock
break;
default:
evt.key = evt.charCode >= evt.KEY_SPACE ? String.fromCharCode(evt.charCode) : evt.keyCode;
}
}else{
evt.key = evt.charCode > 0 ? String.fromCharCode(evt.charCode) : evt.keyCode;
}
}
}
if(dojo.render.html.ie){
if(!evt.target){ evt.target = evt.srcElement; }
if(!evt.currentTarget){ evt.currentTarget = (sender ? sender : evt.srcElement); }
if(!evt.layerX){ evt.layerX = evt.offsetX; }
if(!evt.layerY){ evt.layerY = evt.offsetY; }
// FIXME: scroll position query is duped from dojo.html to avoid dependency on that entire module
// DONOT replace the following to use dojo.body(), in IE, document.documentElement should be used
// here rather than document.body
var doc = (evt.srcElement && evt.srcElement.ownerDocument) ? evt.srcElement.ownerDocument : document;
var docBody = ((dojo.render.html.ie55)||(doc["compatMode"] == "BackCompat")) ? doc.body : doc.documentElement;
if(!evt.pageX){ evt.pageX = evt.clientX + (docBody.scrollLeft || 0) }
if(!evt.pageY){ evt.pageY = evt.clientY + (docBody.scrollTop || 0) }
// mouseover
if(evt.type == "mouseover"){ evt.relatedTarget = evt.fromElement; }
// mouseout
if(evt.type == "mouseout"){ evt.relatedTarget = evt.toElement; }
this.currentEvent = evt;
evt.callListener = this.callListener;
evt.stopPropagation = this._stopPropagation;
evt.preventDefault = this._preventDefault;
}
return evt; // Event
}
this.stopEvent = function(/*Event*/evt){
// summary:
// prevents propigation and clobbers the default action of the
// passed event
// evt: Optional for IE. The native event object.
if(window.event){
evt.cancelBubble = true;
evt.returnValue = false;
}else{
evt.preventDefault();
evt.stopPropagation();
}
}
}