make8bitart.com
at main 296 lines 8.6 kB view raw
1// keymaster.js 2// (c) 2011-2013 Thomas Fuchs 3// keymaster.js may be freely distributed under the MIT license. 4 5;(function(global){ 6 var k, 7 _handlers = {}, 8 _mods = { 16: false, 18: false, 17: false, 91: false }, 9 _scope = 'all', 10 // modifier keys 11 _MODIFIERS = { 12 '⇧': 16, shift: 16, 13 '⌥': 18, alt: 18, option: 18, 14 '⌃': 17, ctrl: 17, control: 17, 15 '⌘': 91, command: 91 16 }, 17 // special keys 18 _MAP = { 19 backspace: 8, tab: 9, clear: 12, 20 enter: 13, 'return': 13, 21 esc: 27, escape: 27, space: 32, 22 left: 37, up: 38, 23 right: 39, down: 40, 24 del: 46, 'delete': 46, 25 home: 36, end: 35, 26 pageup: 33, pagedown: 34, 27 ',': 188, '.': 190, '/': 191, 28 '`': 192, '-': 189, '=': 187, 29 ';': 186, '\'': 222, 30 '[': 219, ']': 221, '\\': 220 31 }, 32 code = function(x){ 33 return _MAP[x] || x.toUpperCase().charCodeAt(0); 34 }, 35 _downKeys = []; 36 37 for(k=1;k<20;k++) _MAP['f'+k] = 111+k; 38 39 // IE doesn't support Array#indexOf, so have a simple replacement 40 function index(array, item){ 41 var i = array.length; 42 while(i--) if(array[i]===item) return i; 43 return -1; 44 } 45 46 // for comparing mods before unassignment 47 function compareArray(a1, a2) { 48 if (a1.length != a2.length) return false; 49 for (var i = 0; i < a1.length; i++) { 50 if (a1[i] !== a2[i]) return false; 51 } 52 return true; 53 } 54 55 var modifierMap = { 56 16:'shiftKey', 57 18:'altKey', 58 17:'ctrlKey', 59 91:'metaKey' 60 }; 61 function updateModifierKey(event) { 62 for(k in _mods) _mods[k] = event[modifierMap[k]]; 63 }; 64 65 // handle keydown event 66 function dispatch(event) { 67 var key, handler, k, i, modifiersMatch, scope; 68 key = event.keyCode; 69 70 if (index(_downKeys, key) == -1) { 71 _downKeys.push(key); 72 } 73 74 // if a modifier key, set the key.<modifierkeyname> property to true and return 75 if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko 76 if(key in _mods) { 77 _mods[key] = true; 78 // 'assignKey' from inside this closure is exported to window.key 79 for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true; 80 return; 81 } 82 updateModifierKey(event); 83 84 // see if we need to ignore the keypress (filter() can can be overridden) 85 // by default ignore key presses if a select, textarea, or input is focused 86 if(!assignKey.filter.call(this, event)) return; 87 88 // abort if no potentially matching shortcuts found 89 if (!(key in _handlers)) return; 90 91 scope = getScope(); 92 93 // for each potential shortcut 94 for (i = 0; i < _handlers[key].length; i++) { 95 handler = _handlers[key][i]; 96 97 // see if it's in the current scope 98 if(handler.scope == scope || handler.scope == 'all'){ 99 // check if modifiers match if any 100 modifiersMatch = handler.mods.length > 0; 101 for(k in _mods) 102 if((!_mods[k] && index(handler.mods, +k) > -1) || 103 (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false; 104 // call the handler and stop the event if neccessary 105 if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){ 106 if(handler.method(event, handler)===false){ 107 if(event.preventDefault) event.preventDefault(); 108 else event.returnValue = false; 109 if(event.stopPropagation) event.stopPropagation(); 110 if(event.cancelBubble) event.cancelBubble = true; 111 } 112 } 113 } 114 } 115 }; 116 117 // unset modifier keys on keyup 118 function clearModifier(event){ 119 var key = event.keyCode, k, 120 i = index(_downKeys, key); 121 122 // remove key from _downKeys 123 if (i >= 0) { 124 _downKeys.splice(i, 1); 125 } 126 127 if(key == 93 || key == 224) key = 91; 128 if(key in _mods) { 129 _mods[key] = false; 130 for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false; 131 } 132 }; 133 134 function resetModifiers() { 135 for(k in _mods) _mods[k] = false; 136 for(k in _MODIFIERS) assignKey[k] = false; 137 }; 138 139 // parse and assign shortcut 140 function assignKey(key, scope, method){ 141 var keys, mods; 142 keys = getKeys(key); 143 if (method === undefined) { 144 method = scope; 145 scope = 'all'; 146 } 147 148 // for each shortcut 149 for (var i = 0; i < keys.length; i++) { 150 // set modifier keys if any 151 mods = []; 152 key = keys[i].split('+'); 153 if (key.length > 1){ 154 mods = getMods(key); 155 key = [key[key.length-1]]; 156 } 157 // convert to keycode and... 158 key = key[0] 159 key = code(key); 160 // ...store handler 161 if (!(key in _handlers)) _handlers[key] = []; 162 _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods }); 163 } 164 }; 165 166 // unbind all handlers for given key in current scope 167 function unbindKey(key, scope) { 168 var multipleKeys, keys, 169 mods = [], 170 i, j, obj; 171 172 multipleKeys = getKeys(key); 173 174 for (j = 0; j < multipleKeys.length; j++) { 175 keys = multipleKeys[j].split('+'); 176 177 if (keys.length > 1) { 178 mods = getMods(keys); 179 key = keys[keys.length - 1]; 180 } 181 182 key = code(key); 183 184 if (scope === undefined) { 185 scope = getScope(); 186 } 187 if (!_handlers[key]) { 188 return; 189 } 190 for (i in _handlers[key]) { 191 obj = _handlers[key][i]; 192 // only clear handlers if correct scope and mods match 193 if (obj.scope === scope && compareArray(obj.mods, mods)) { 194 _handlers[key][i] = {}; 195 } 196 } 197 } 198 }; 199 200 // Returns true if the key with code 'keyCode' is currently down 201 // Converts strings into key codes. 202 function isPressed(keyCode) { 203 if (typeof(keyCode)=='string') { 204 keyCode = code(keyCode); 205 } 206 return index(_downKeys, keyCode) != -1; 207 } 208 209 function getPressedKeyCodes() { 210 return _downKeys.slice(0); 211 } 212 213 function filter(event){ 214 var tagName = (event.target || event.srcElement).tagName; 215 // ignore keypressed in any elements that support keyboard data input 216 return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); 217 } 218 219 // initialize key.<modifier> to false 220 for(k in _MODIFIERS) assignKey[k] = false; 221 222 // set current scope (default 'all') 223 function setScope(scope){ _scope = scope || 'all' }; 224 function getScope(){ return _scope || 'all' }; 225 226 // delete all handlers for a given scope 227 function deleteScope(scope){ 228 var key, handlers, i; 229 230 for (key in _handlers) { 231 handlers = _handlers[key]; 232 for (i = 0; i < handlers.length; ) { 233 if (handlers[i].scope === scope) handlers.splice(i, 1); 234 else i++; 235 } 236 } 237 }; 238 239 // abstract key logic for assign and unassign 240 function getKeys(key) { 241 var keys; 242 key = key.replace(/\s/g, ''); 243 keys = key.split(','); 244 if ((keys[keys.length - 1]) == '') { 245 keys[keys.length - 2] += ','; 246 } 247 return keys; 248 } 249 250 // abstract mods logic for assign and unassign 251 function getMods(key) { 252 var mods = key.slice(0, key.length - 1); 253 for (var mi = 0; mi < mods.length; mi++) 254 mods[mi] = _MODIFIERS[mods[mi]]; 255 return mods; 256 } 257 258 // cross-browser events 259 function addEvent(object, event, method) { 260 if (object.addEventListener) 261 object.addEventListener(event, method, false); 262 else if(object.attachEvent) 263 object.attachEvent('on'+event, function(){ method(window.event) }); 264 }; 265 266 // set the handlers globally on document 267 addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48 268 addEvent(document, 'keyup', clearModifier); 269 270 // reset modifiers to false whenever the window is (re)focused. 271 addEvent(window, 'focus', resetModifiers); 272 273 // store previously defined key 274 var previousKey = global.key; 275 276 // restore previously defined key and return reference to our key object 277 function noConflict() { 278 var k = global.key; 279 global.key = previousKey; 280 return k; 281 } 282 283 // set window.key and window.key.set/get/deleteScope, and the default filter 284 global.key = assignKey; 285 global.key.setScope = setScope; 286 global.key.getScope = getScope; 287 global.key.deleteScope = deleteScope; 288 global.key.filter = filter; 289 global.key.isPressed = isPressed; 290 global.key.getPressedKeyCodes = getPressedKeyCodes; 291 global.key.noConflict = noConflict; 292 global.key.unbind = unbindKey; 293 294 if(typeof module !== 'undefined') module.exports = key; 295 296})(this);