/*
* Acunote Shortcuts.
* Javascript keyboard shortcuts mini-framework.
*
* Copyright (c) 2007-2008 Pluron, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Modified by Nick Robinson
* * can add shortcuts one at a time, rather than in one big hash
* * removed the global window.SHORTCUTS and replaced with this.root
* * check if the shortcut_status div already exists, if so don't create it (allows apps freedom of where to put it).
*/
var shortcutListener = {
listen : true,
root : null,
onEscape : null,
shortcut : null,
combination : '',
lastKeypress : 0,
clearTimeout : 2000,
// Keys we don't listen
keys : {
KEY_BACKSPACE : 8,
KEY_TAB : 9,
KEY_ENTER : 13,
KEY_SHIFT : 16,
KEY_CTRL : 17,
KEY_ALT : 18,
KEY_ESC : 27,
KEY_SPACE : 32,
KEY_LEFT : 37,
KEY_UP : 38,
KEY_RIGHT : 39,
KEY_DOWN : 40,
KEY_DELETE : 46,
KEY_HOME : 36,
KEY_END : 35,
KEY_PAGEUP : 33,
KEY_PAGEDOWN : 34
},
init : function() {
if (!this.root)
return false;
if (document.getElementById('shortcut_status') == null) {
this.createStatusArea();
}
this.setObserver();
},
add : function(combo /* array of keys */, func) {
if (!this.root) {
this.root = {}
}
var shortcut = this.root;
for (var i = 0; i < combo.length; i++) {
var key = combo[i].toLowerCase();
if (!shortcut[key]) {
if (i == combo.length - 1) {
shortcut[key] = func;
} else {
shortcut[key] = {};
}
} else {
if (i == combo.length - 1) {
alert("shortcut '" + combo + "' clashes with one or more longer shortcuts.");
return;
} else {
if (typeof (shortcut[key]) == "function") {
alert("shortcut '" + combo + "' clashes with an existing shortcut length " + (i + 1));
return;
}
}
}
shortcut = shortcut[key];
}
},
isInputTarget : function(e) {
var target = e.target || e.srcElement;
if (target && target.nodeName) {
var targetNodeName = target.nodeName.toLowerCase();
if (targetNodeName == "textarea"
|| targetNodeName == "select"
|| (targetNodeName == "input" && target.type && (target.type.toLowerCase() == "text" || target.type.toLowerCase() == "password"))) {
return true;
}
}
return false;
},
stopEvent : function(event) {
if (event.preventDefault) {
event.preventDefault();
event.stopPropagation();
} else {
event.returnValue = false;
event.cancelBubble = true;
}
},
// shortcut notification/status area
createStatusArea : function() {
var area = document.createElement('div');
area.setAttribute('id', 'shortcut_status');
area.style.display = 'none';
document.body.appendChild(area);
},
showStatus : function() {
document.getElementById('shortcut_status').style.display = '';
},
hideStatus : function() {
document.getElementById('shortcut_status').style.display = 'none';
},
showCombination : function() {
var bar = document.getElementById('shortcut_status');
bar.innerHTML = this.combination;
this.showStatus();
},
// This method creates event observer for the whole document
// This is the common way of setting event observer that works
// in all modern brwosers with "keypress" fix for
// Konqueror/Safari/KHTML borrowed from Prototype.js
setObserver : function() {
var name = 'keypress';
if (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || document.detachEvent) {
name = 'keydown';
}
if (document.addEventListener) {
document.addEventListener(name, function(e) {
shortcutListener.keyCollector(e)
}, false);
} else if (document.attachEvent) {
document.attachEvent('on' + name, function(e) {
shortcutListener.keyCollector(e)
});
}
},
// Key press collector. Collects all keypresses into combination
// and checks it we have action for it
keyCollector : function(e) {
// do not listen if no shortcuts defined
if (!this.root)
return false;
// do not listen if listener was explicitly turned off
if (!shortcutListener.listen)
return false;
// leave modifiers for browser
if (e.altKey || e.ctrlKey || e.metaKey)
return false;
var keyCode = e.keyCode;
// On escape, clear the combination, and let the application do its
// processing if needed.
if (e.keyCode == 27) {
shortcutListener.clearCombination();
if (this.onEscape) {
this.onEscape();
}
}
// do not listen for Ctrl, Alt, Tab, Space, Esc and others
for ( var key in this.keys) {
if (e.keyCode == this.keys[key])
return false;
}
// do not listen for functional keys
if (navigator.userAgent.match(/Gecko/)) {
if (e.keyCode >= 112 && e.keyCode <= 123)
return false;
}
// do not listen in input/select/textarea fields
if (this.isInputTarget(e))
return false;
// get letter pressed for different browsers
var code = e.which ? e.which : e.keyCode
var letter = String.fromCharCode(code).toLowerCase();
if (e.shiftKey)
letter = letter.toUpperCase();
if (shortcutListener.process(letter))
shortcutListener.stopEvent(e);
},
// process keys
process : function(letter) {
if (!this.root)
return false;
if (!shortcutListener.listen)
return false;
// if no combination then start from the beginning
if (!shortcutListener.shortcut) {
shortcutListener.shortcut = this.root;
}
// if unknown letter then say goodbye
if (!shortcutListener.shortcut[letter])
return false
if (typeof (shortcutListener.shortcut[letter]) == "function") {
shortcutListener.shortcut[letter]();
shortcutListener.clearCombination();
} else {
shortcutListener.shortcut = shortcutListener.shortcut[letter];
// append combination
shortcutListener.combination = shortcutListener.combination + letter;
if (shortcutListener.combination.length > 0) {
shortcutListener.showCombination();
// save last keypress timestamp (for autoclear)
var d = new Date;
shortcutListener.lastKeypress = d.getTime();
// autoclear combination in 2 seconds
setTimeout("shortcutListener.clearCombinationOnTimeout()", shortcutListener.clearTimeout);
}
;
}
return true;
},
// clear combination
clearCombination : function() {
shortcutListener.shortcut = null;
shortcutListener.combination = '';
this.hideStatus();
},
clearCombinationOnTimeout : function() {
var d = new Date;
// check if last keypress was earlier than (now - clearTimeout)
// 100ms here is used just to be sure that this will work in superfast
// browsers :)
if ((d.getTime() - shortcutListener.lastKeypress) >= (shortcutListener.clearTimeout - 100)) {
shortcutListener.clearCombination();
}
}
}
// Looks for the nearest parent with a class name of 'ww_minimized' or
// 'ww_maximized', and changes its class to 'closed'.
// With the correct style sheet, this will hide the whole section
// (not only the minimizable content).
// This will mean that you can't "unclose" the object.
function ww_doClose(event) {
var ele = ww_getMinimizer(event);
if (ele) {
ele.className = "ww_closed";
}
return false; // Stops browser from processing the <a> tag and further.
}
// Looks for the nearest parent with a class name of 'ww_minimized' or
// 'ww_maximized', and changes its class to toggles its class between
// 'ww_minimized' and 'ww_maximized'
function ww_doToggleMinimize(event) {
var ele = ww_getMinimizer(event);
if (ele) {
if (ele.className == "ww_minimized") {
ele.className = "ww_maximized";
} else {
ele.className = "ww_minimized";
}
}
return false;
}
// Looks for the nearest parent with a class name of 'ww_minimized' or
// 'ww_maximized', and changes its class to 'ww_minimized'.
// With the correct style sheet, this will hide any inner sections with a
// class name of 'ww_minimizable'.
function ww_doMinimize(event) {
var ele = ww_getMinimizer(event);
if (ele) {
ele.className = "ww_minimized";
}
return false;
}
// Looks for the nearest parent with a class name of 'ww_minimized' or
// 'ww_maximized', and changes its class to 'ww_minimized'.
// This undoes the behaviour of doMinimize.
function ww_doMaximize(event) {
var ele = ww_getMinimizer(event);
if (ele) {
ele.className = "ww_maximized";
}
return false;
}
// PRIVATE
// Looks for the nearest parent with a class name of 'ww_minimized' or
// 'ww_maximized'. Returns null if one is not found.
function ww_getMinimizer(event) {
var ele;
if (event.srcElement) {
ele = event.srcElement;
} else {
ele = event.target;
}
if (ele) {
do {
ele = ele.parentNode;
} while (ele && (ele.className != 'ww_minimized') && (ele.className != 'ww_maximized'));
}
return ele;
}
// Finds an object by name. First looks at the documents attributes,
// then at document.all, and then iterates over all form objects.
function ww_findObj(n, d) {
// If we already have an Element, rather than an ID, return it.
if (n instanceof Element) {
return n;
}
var i, x;
if (d == null) {
d = document;
}
// First try the document's attributes
if (!(x = d[n]) && d.getElementById) {
// Then try the 'all' array - if its defined.
x = d.getElementById(n);
} else if (!(x = d[n]) && d.all) {
// Then try the 'all' array - if its defined.
x = d.all[n];
}
// If still not found, look through ever form object.
for (i = 0; !x && i < d.forms.length; i++) {
x = d.forms[i][n];
}
return x;
}
// The following viewport functions were described here :
// http://www.quirksmode.org/viewport/compatibility.html
// Not copied verbatum, so any errors are my own.
function ww_innerWidth() {
if (self.innerWidth) {
return self.innerWidth;
} else if (document.documentElement && document.documentElement.clientWidth) {
return document.documentElement.clientWidth;
} else if (document.body) {
return document.body.clientWidth;
} else {
return "ww_innerWidth Failed";
}
}
function ww_innerHeight() {
if (self.innerHeight) {
return self.innerHeight;
} else if (document.documentElement && document.documentElement.clientHeight) {
return document.documentElement.clientHeight;
} else if (document.body) {
return document.body.clientHeight;
} else {
return "ww_innerHeight Failed";
}
}
function ww_scrollX() {
if (self.pageXOffset) {
return self.pageXOffset;
} else if (document.documentElement && document.documentElement.scrollLeft) {
return document.documentElement.scrollLeft;
} else if (document.body) {
return document.body.scrollLeft;
} else {
return "ww_scrollX failed";
}
}
function ww_scrollY() {
if (self.pageYOffset) {
return self.pageYOffset;
} else if (document.documentElement && document.documentElement.scrollTop) {
return document.documentElement.scrollTop;
} else if (document.body) {
return document.body.scrollTop;
} else {
return "ww_scrollY failed";
}
}
// End of viewport functions.
function ww_getEventElement(event) {
var ele;
if (event.srcElement) {
return event.srcElement;
} else {
return event.target;
}
}
// begin IMAGE ROLLOVER functions
function ww_changeImage(img, imageUrl) {
if (!img.oldSrc) {
img.ww_oldSrc = img.src;
}
img.src = imageUrl;
}
function ww_restoreImage(img) {
if (img.ww_oldSrc) {
img.src = img.ww_oldSrc;
}
}
ww_imageCache = new Array();
function ww_cacheImage(imageUrl) {
var image = new Image();
image.src = imageUrl;
ww_imageCache[ww_imageCache.length] = image;
}
// end IMAGE ROLLOVER functions
function ww_dump(obj, depth, ind) {
if (depth == null)
depth = 1;
if (!ind)
ind = '';
if (depth < 1)
return "...";
var r = '{\n';
for ( var n in obj) {
var value = obj[n];
r += ind + ' [' + n + '] : ';
if (typeof (value) == "object") {
r += ww_dump(value, depth - 1, ind + " ") + '\n';
} else {
r += value + '\n';
}
}
r += ind + '}';
return r;
}
function ww_followLink(link) {
link = ww_findObj(link);
if (link != null) {
if ((link.onclick == null) || link.onclick()) {
document.location = link;
}
}
}
function ww_pressButton(ele) {
ele = ww_findObj(ele);
if (ele != null) {
ele.click();
}
}
function ww_findFirst(ele, nodeName) {
ele = ww_findObj(ele);
if (ele == null) {
return null;
}
for (var i = 0; i < ele.childNodes.length; i++) {
var child = ele.childNodes[i];
if (child.nodeName == nodeName) {
return child;
}
var recurse = ww_findFirst(child, nodeName);
if (recurse != null) {
return recurse;
}
}
return null;
}
function ww_focusFirstLink(ele) {
var link = ww_findFirst(ele, "A");
if (link != null) {
link.focus();
return true;
}
return false;
}