fastladder/fastladder

View on GitHub
app/assets/javascripts/lib/events/hotkey.js

Summary

Maintainability
D
1 day
Test Coverage
/*
 hotkey.js
  usage :
   var kb = new HotKey;
   kb.add("a",function(){alert("a")});
   kb.add("A",function(){alert("Shift+a")});
*/
function HotKey(element, name){
    var ctor = arguments.callee;
    var target = element || document;
    this._target = target;
    this._keyfunc = {};
    this._ctor = ctor;
    if(name){
        ctor.keysets[name] = this;
    }
    // attach event
    if(!ctor.Base.initialized){
        Event.observe(target, "keydown",  ctor.Base.invoke_keydown, true);
        Event.observe(target, "keypress", ctor.Base.invoke_keypress, true);
        ctor.Base.initialized = true;
    }
    this.active = true;
    this.init();
}

HotKey.register_keylistener = function(handler, f){
    if(handler == "keydown"){
        this.Base.KeydownListeners.push(f)
    }
    if(handler == "keypress"){
        this.Base.KeypressListeners.push(f)
    }
};

HotKey.Base = {
    initialized : false,
    KeydownListeners  : [],
    KeypressListeners : [],
    invoke_keydown : function(e){
        var listeners = HotKey.Base.KeydownListeners;
        for(var i=0;i<listeners.length;i++){
            listeners[i].call(this, e);
        }
    },
    invoke_keypress : function(e){
        var listeners = HotKey.Base.KeypressListeners;
        for(var i=0;i<listeners.length;i++){
            listeners[i].call(this, e);
        }
    }
};

// keycode
HotKey.kc2char = function(kc){
    var between = function(a,b){
        return a <= kc && kc <= b
    }
    var _32_40 = "space pageup pagedown end home left up right down".split(" ");
    var kt = {
        8  : "back",
        9  : "tab",
        10 : "enter",
        13 : "enter",
        16 : "shift",
        17 : "ctrl",
        58 : ":", // keypress
        60 : "<", // keypress
        62 : ">", // keypress
        63 : "?", // keypress
        229: "IME"
    };
    return (
        between(65,90)  ? String.fromCharCode(kc+32) : // keydown  a-z
        between(97,122) ? String.fromCharCode(kc) :    // keypress a-z
        between(48,57)  ? String.fromCharCode(kc) :    // 0-9
        between(96,105) ? String.fromCharCode(kc-48) : // num 0-9
        between(32,40)  ? _32_40[kc-32] :
        kt.hasOwnProperty(kc) ? kt[kc] : 
        "null"
    )
}

HotKey.specialCase = function(e){
    var kc = e.keyCode;
    if(e.type == "keypress" && e.keyCode == 27) return "esc";
    if(e.type == "keydown" && e.keyCode == 46)  return "delete";
    if(112 <= e.keyCode && e.keyCode <= 123){
         if(e.type  == "keydown"){return "f"+ (kc - 111)}
         if(e.which == 0){return "f"+ (kc - 111)}
    }
    return false;
}

// printableなキーが押されたかどうかを判別する
HotKey.isPrintable = function(e){
    var c = HotKey.getChar(e);
    // 対応してないキー
    if(!c) return true;
    if(/^[0-9a-z]{1,1}$/.test(c)) return true;
    if(/IME|space|\>|\<|\?/i.test(c)) return true;
    return false;
}


// keypress, keydown
// keycode , which
// IE, Opera, Firefox, Safari
HotKey.getChar = function(e){
    var c = HotKey.specialCase(e);
    if(c) return c;
    var between = function(a,b){
        return a <= kc && kc <= b
    }
    var kc = e.keyCode || e.which;
    if(e.keyCode){
        return HotKey.kc2char(kc)
    } else if(e.which){
        return HotKey.kc2char(kc);
    }
};

// キーセットの切り替え
HotKey.keysets = {};
HotKey.use_only = function(name){
    var keysets = this.keysets;
    if(!keysets.hasOwnProperty(name)) return;
    for(var i in keysets){
        keysets[i].activate(false);
    }
    setTimeout(function(){
        keysets[name].activate(true);
    }, 0);
};

HotKey.prototype.globalCallback = function(){};
HotKey.prototype.ignore = /input|textarea/i;
HotKey.prototype.allow  = /element_id/;
HotKey.prototype.filter = function(e){ return true };
HotKey.prototype.abort = true;
HotKey.prototype.init = function(){
    var self = this;
    var target = this._target;
    var cancelNext;
    var state = "";
    var count = 0;
    //var log = [];
    // keydown -> keypress
    var keydown_listener = function(e){
        if(!self.active) return;
        self.globalCallback();
        //window.status = count++ + "keydown";
        if(window.opera){Event.stop(e);return}
        if(e.metaKey || e.altKey) {return}
        self.event = e;
        self.lastInput = self.get_input(e);
        self.lastCapture = "keydown";
        if(self.invoke()){
            cancelNext = true;
        } else {
            cancelNext = false;
            self.lastCapture  = "";
        }
        // log.push(self.lastInput);
    };

    var keypress_listener = function(e){
        if(!self.active) return;
        self.globalCallback();
        if(e.metaKey || e.altKey) {return}
        if(cancelNext){
            cancelNext = false;
            self.lastCapture = "keypress";
            Event.stop(e);
            return;
        }
        self.event = e;
        var input = self.get_input(e);
        //window.status = count++  + "keypress"+ input;
        if(self.lastCapture != "keydown" || self.lastInput != input){
            self.lastInput = input;
            self.lastCapture = "keypress";
            // log.push(self.lastInput)
            self.invoke();
        }
    };

    // keypress listener
    this._ctor.register_keylistener("keydown", keydown_listener, self);
    this._ctor.register_keylistener("keypress", keypress_listener, self);
};

HotKey.prototype.invoke = function(input){
    input = input || this.lastInput;
    var e = this.event;
    if(!this._keyfunc.hasOwnProperty(input)) return false;
    if(typeof this._keyfunc[input] != "function") return false;
    // abort browser action
    this.abort && Event.stop(e);
    this._keyfunc[input].call(this, e);
    this.lastInvoke = input;
    return true;
};

HotKey.prototype.get_input = function(e){
    var el  = (e.target || e.srcElement);
    var tag = el.tagName;
    var id  = el.id;
    if(!this.allow.test(id) && this.ignore.test(tag)) return;
    // filter
    if(!this.filter(e)) return false;
    var input = HotKey.getChar(e) + "";
    // window.status = [e.type, e.keyCode,e.button, e.which,input];
    if(e.shiftKey && input != "shift"){
        input = (input.length == 1) ? input.toUpperCase() :    "shift+" + input;
    }
    if(e.ctrlKey && !/ctrl/i.test(input))
         input = "ctrl+" + input;
    return input;
};

HotKey.prototype.sendKey = function(key){
    this._keyfunc[key] && this._keyfunc[key]()
};

HotKey.prototype.add = function(key, func){
    if(key.constructor == Array){
        for(var i=0;i<key.length;i++)
            this.add(key[i], func)
    }else if(key.indexOf("|") != -1){
        this.add(key.split("|"), func);
    }else{
        this._keyfunc[key] = func;
    }
};

HotKey.prototype.remove = function(key){
    delete this._keyfunc[key];
    return this;
};

HotKey.prototype.activate = function(sw){
    this.active = sw;
    return this;
};

HotKey.prototype.clear = function(){
    this._keyfunc = {};
};