Exit Full View

ntcslash / src / main / webapp / ww_resources / webwidgets.js

/*
 *  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;
}