Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * jwerty - Awesome handling of keyboard events
- *
- * jwerty is a JS lib which allows you to bind, fire and assert key combination
- * strings against elements and events. It normalises the poor std api into
- * something easy to use and clear.
- *
- * This code is licensed under the MIT
- * For the full license see: http://keithamus.mit-license.org/
- * For more information see: http://keithamus.github.com/jwerty
- *
- * @author Keith Cirkel ('keithamus') <[email protected]>
- * @license http://keithamus.mit-license.org/
- * @copyright Copyright © 2011, Keith Cirkel
- *
- */
- (function (global, exports) {
- // Try require external librairies in Node.js context
- function tryRequire(mod) {
- if (typeof require == 'function' && typeof module !== 'undefined' && module.exports) {
- try {
- return require(mod.toLowerCase());
- } catch (err) {}
- } else {
- return global[mod];
- }
- }
- // Helper methods & vars:
- var $d = global.document,
- $ = (tryRequire('jQuery') || tryRequire('Zepto') || tryRequire('ender') || $d),
- $$, // Element selector function
- $b, // Event binding function
- $u, // Event unbinding function
- $f, // Event firing function
- ke = 'keydown';
- function realTypeOf(v, s) {
- return (v === null) ? s === 'null'
- : (v === undefined) ? s === 'undefined'
- : (v.is && v instanceof $) ? s === 'element'
- : Object.prototype.toString.call(v).toLowerCase().indexOf(s) > 7;
- }
- if ($ === $d) {
- $$ = function (selector, context) {
- return selector ? $.querySelector(selector, context || $) : $;
- };
- $b = function (e, fn) { e.addEventListener(ke, fn, false); };
- $u = function (e, fn) { e.removeEventListener(ke, fn, false); };
- $f = function (e, jwertyEv) {
- var ret = $d.createEvent('Event'),
- i;
- ret.initEvent(ke, true, true);
- for (i in jwertyEv) ret[i] = jwertyEv[i];
- return (e || $).dispatchEvent(ret);
- };
- } else {
- $$ = function (selector, context) { return $(selector || $d, context); };
- $b = function (e, fn) { $(e).bind(ke + '.jwerty', fn); };
- $u = function (e, fn) { $(e).unbind(ke + '.jwerty', fn) };
- $f = function (e, ob) { $(e || $d).trigger($.Event(ke, ob)); };
- }
- // Private
- var _modProps = { 16: 'shiftKey', 17: 'ctrlKey', 18: 'altKey', 91: 'metaKey' };
- // Generate key mappings for common keys that are not printable.
- var _keys = {
- // MOD aka toggleable keys
- mods: {
- // Shift key, ?
- '?': 16,
- shift: 16,
- // CTRL key, on Mac: ^
- '^': 17,
- ctrl: 17,
- // ALT key, on Mac: ? (Alt)
- '?': 18,
- alt: 18,
- option: 18,
- // META, on Mac: ? (CMD), on Windows (Win), on Linux (Super)
- '?': 91,
- meta: 91,
- cmd: 91,
- 'super': 91,
- win: 91
- },
- // Normal keys
- keys: {
- // Backspace key, on Mac: ? (Backspace)
- '?': 8,
- backspace: 8,
- // Tab Key, on Mac: ? (Tab), on Windows ??
- '?': 9,
- '?': 9,
- tab: 9,
- // Return key, ?
- '?': 13,
- 'return': 13,
- enter: 13,
- '?': 13,
- // Pause/Break key
- 'pause': 19,
- 'pause-break': 19,
- // Caps Lock key, ?
- '?': 20,
- caps: 20,
- 'caps-lock': 20,
- // Escape key, on Mac: ?, on Windows: Esc
- '?': 27,
- escape: 27,
- esc: 27,
- // Space key
- space: 32,
- // Page-Up key, or pgup, on Mac: ?
- '?': 33,
- pgup: 33,
- 'page-up': 33,
- // Page-Down key, or pgdown, on Mac: ?
- '?': 34,
- pgdown: 34,
- 'page-down': 34,
- // END key, on Mac: ?
- '?': 35,
- end: 35,
- // HOME key, on Mac: ?
- '?': 36,
- home: 36,
- // Insert key, or ins
- ins: 45,
- insert: 45,
- // Delete key, on Mac: ? (Delete)
- del: 46,
- 'delete': 46,
- // Left Arrow Key, or ?
- '?': 37,
- left: 37,
- 'arrow-left': 37,
- // Up Arrow Key, or ?
- '?': 38,
- up: 38,
- 'arrow-up': 38,
- // Right Arrow Key, or ?
- '?': 39,
- right: 39,
- 'arrow-right': 39,
- // Up Arrow Key, or ?
- '?': 40,
- down: 40,
- 'arrow-down': 40,
- // odities, printing characters that come out wrong:
- // Num-Multiply, or *
- '*': 106,
- star: 106,
- asterisk: 106,
- multiply: 106,
- // Num-Plus or +
- '+': 107,
- 'plus': 107,
- // Num-Subtract, or -
- '-': 109,
- subtract: 109,
- 'num-.': 110,
- 'num-period': 110,
- 'num-dot': 110,
- 'num-full-stop': 110,
- 'num-delete': 110,
- // Semicolon
- ';': 186,
- semicolon: 186,
- // = or equals
- '=': 187,
- 'equals': 187,
- // Comma, or ,
- ',': 188,
- comma: 188,
- //'-': 189, //???
- // Period, or ., or full-stop
- '.': 190,
- period: 190,
- 'full-stop': 190,
- // Slash, or /, or forward-slash
- '/': 191,
- slash: 191,
- 'forward-slash': 191,
- // Tick, or `, or back-quote
- '`': 192,
- tick: 192,
- 'back-quote': 192,
- // Open bracket, or [
- '[': 219,
- 'open-bracket': 219,
- // Back slash, or \
- '\\': 220,
- 'back-slash': 220,
- // Close backet, or ]
- ']': 221,
- 'close-bracket': 221,
- // Apostraphe, or Quote, or '
- '\'': 222,
- quote: 222,
- apostraphe: 222
- }
- };
- // To minimise code bloat, add all of the 0-9 and NUMPAD 0-9 keys in a loop
- var i = 47,
- n = 0;
- while (++i < 106) {
- _keys.keys[n] = i;
- _keys.keys['num-' + n] = i + 48;
- ++n;
- }
- // To minimise code bloat, add all of the F1-F25 keys in a loop
- i = 111,
- n = 1;
- while (++i < 136) {
- _keys.keys['f' + n] = i;
- ++n;
- }
- // To minimise code bloat, add all of the letters of the alphabet in a loop
- i = 64;
- while (++i < 91) {
- _keys.keys[String.fromCharCode(i).toLowerCase()] = i;
- }
- function JwertyCode(jwertyCode) {
- var i,
- c,
- n,
- z,
- keyCombo,
- optionals,
- jwertyCodeFragment,
- rangeMatches,
- rangeI;
- // In-case we get called with an instance of ourselves, just return that.
- if (jwertyCode instanceof JwertyCode) return jwertyCode;
- // If jwertyCode isn't an array, cast it as a string and split into array.
- if (!realTypeOf(jwertyCode, 'array')) {
- jwertyCode = (String(jwertyCode)).replace(/\s/g, '').toLowerCase()
- .match(/(?:\+,|[^,])+/g);
- }
- // Loop through each key sequence in jwertyCode
- for (i = 0, c = jwertyCode.length; i < c; ++i) {
- // If the key combo at this part of the sequence isn't an array,
- // cast as a string and split into an array.
- if (!realTypeOf(jwertyCode[i], 'array')) {
- jwertyCode[i] = String(jwertyCode[i])
- .match(/(?:\+\/|[^\/])+/g);
- }
- // Parse the key optionals in this sequence
- optionals = [],
- n = jwertyCode[i].length;
- while (n--) {
- // Begin creating the object for this key combo
- jwertyCodeFragment = jwertyCode[i][n];
- keyCombo = {
- jwertyCombo: String(jwertyCodeFragment),
- shiftKey: false,
- ctrlKey: false,
- altKey: false,
- metaKey: false
- };
- // If jwertyCodeFragment isn't an array then cast as a string
- // and split it into one.
- if (!realTypeOf(jwertyCodeFragment, 'array')) {
- jwertyCodeFragment = String(jwertyCodeFragment).toLowerCase()
- .match(/(?:(?:[^\+])+|\+\+|^\+$)/g);
- }
- z = jwertyCodeFragment.length;
- while (z--) {
- // Normalise matching errors
- if (jwertyCodeFragment[z] === '++') jwertyCodeFragment[z] = '+';
- // Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
- if (jwertyCodeFragment[z] in _keys.mods) {
- keyCombo[_modProps[_keys.mods[jwertyCodeFragment[z]]]] = true;
- } else if (jwertyCodeFragment[z] in _keys.keys) {
- keyCombo.keyCode = _keys.keys[jwertyCodeFragment[z]];
- } else {
- rangeMatches = jwertyCodeFragment[z].match(/^\[([^-]+\-?[^-]*)-([^-]+\-?[^-]*)\]$/);
- }
- }
- if (realTypeOf(keyCombo.keyCode, 'undefined')) {
- // If we picked up a range match earlier...
- if (rangeMatches && (rangeMatches[1] in _keys.keys) && (rangeMatches[2] in _keys.keys)) {
- rangeMatches[2] = _keys.keys[rangeMatches[2]];
- rangeMatches[1] = _keys.keys[rangeMatches[1]];
- // Go from match 1 and capture all key-comobs up to match 2
- for (rangeI = rangeMatches[1]; rangeI < rangeMatches[2]; ++rangeI) {
- optionals.push({
- altKey: keyCombo.altKey,
- shiftKey: keyCombo.shiftKey,
- metaKey: keyCombo.metaKey,
- ctrlKey: keyCombo.ctrlKey,
- keyCode: rangeI,
- jwertyCombo: String(jwertyCodeFragment)
- });
- }
- keyCombo.keyCode = rangeI;
- // Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
- } else {
- keyCombo.keyCode = 0;
- }
- }
- optionals.push(keyCombo);
- }
- this[i] = optionals;
- }
- this.length = i;
- return this;
- }
- var jwerty = exports.jwerty = {
- /**
- * jwerty.event
- *
- * `jwerty.event` will return a function, which expects the first
- * argument to be a key event. When the key event matches `jwertyCode`,
- * `callbackFunction` is fired. `jwerty.event` is used by `jwerty.key`
- * to bind the function it returns. `jwerty.event` is useful for
- * attaching to your own event listeners. It can be used as a decorator
- * method to encapsulate functionality that you only want to fire after
- * a specific key combo. If `callbackContext` is specified then it will
- * be supplied as `callbackFunction`'s context - in other words, the
- * keyword `this` will be set to `callbackContext` inside the
- * `callbackFunction` function.
- *
- * @param {Mixed} jwertyCode can be an array, or string of key
- * combinations, which includes optinals and or sequences
- * @param {Function} callbackFucntion is a function (or boolean) which
- * is fired when jwertyCode is matched. Return false to
- * preventDefault()
- * @param {Object} callbackContext (Optional) The context to call
- * `callback` with (i.e this)
- *
- */
- event: function (jwertyCode, callbackFunction, callbackContext /*? this */) {
- // Construct a function out of callbackFunction, if it is a boolean.
- if (realTypeOf(callbackFunction, 'boolean')) {
- var bool = callbackFunction;
- callbackFunction = function () { return bool; };
- }
- jwertyCode = new JwertyCode(jwertyCode);
- // Initialise in-scope vars.
- var i = 0,
- c = jwertyCode.length - 1,
- returnValue,
- jwertyCodeIs;
- // This is the event listener function that gets returned...
- return function (event) {
- // if jwertyCodeIs returns truthy (string)...
- if ((jwertyCodeIs = jwerty.is(jwertyCode, event, i))) {
- // ... and this isn't the last key in the sequence,
- // incriment the key in sequence to check.
- if (i < c) {
- ++i;
- return;
- // ... and this is the last in the sequence (or the only
- // one in sequence), then fire the callback
- } else {
- returnValue = callbackFunction.call(
- callbackContext || this, event, jwertyCodeIs);
- // If the callback returned false, then we should run
- // preventDefault();
- if (returnValue === false) event.preventDefault();
- // Reset i for the next sequence to fire.
- i = 0;
- return;
- }
- }
- // If the event didn't hit this time, we should reset i to 0,
- // that is, unless this combo was the first in the sequence,
- // in which case we should reset i to 1.
- i = jwerty.is(jwertyCode, event) ? 1 : 0;
- };
- },
- /**
- * jwerty.is
- *
- * `jwerty.is` will return a boolean value, based on if `event` matches
- * `jwertyCode`. `jwerty.is` is called by `jwerty.event` to check
- * whether or not to fire the callback. `event` can be a DOM event, or
- * a jQuery/Zepto/Ender manufactured event. The properties of
- * `jwertyCode` (speficially ctrlKey, altKey, metaKey, shiftKey and
- * keyCode) should match `jwertyCode`'s properties - if they do, then
- * `jwerty.is` will return `true`. If they don't, `jwerty.is` will
- * return `false`.
- *
- * @param {Mixed} jwertyCode can be an array, or string of key
- * combinations, which includes optinals and or sequences
- * @param {KeyboardEvent} event is the KeyboardEvent to assert against
- * @param {Integer} i (Optional) checks the `i` key in jwertyCode
- * sequence
- *
- */
- is: function (jwertyCode, event, i /*? 0*/) {
- jwertyCode = new JwertyCode(jwertyCode);
- // Default `i` to 0
- i = i || 0;
- // We are only interested in `i` of jwertyCode;
- jwertyCode = jwertyCode[i];
- // jQuery stores the *real* event in `originalEvent`, which we use
- // because it does annoything stuff to `metaKey`
- event = event.originalEvent || event;
- // We'll look at each optional in this jwertyCode sequence...
- var n = jwertyCode.length,
- returnValue = false;
- // Loop through each fragment of jwertyCode
- while (n--) {
- returnValue = jwertyCode[n].jwertyCombo;
- // For each property in the jwertyCode object, compare to `event`
- for (var p in jwertyCode[n]) {
- // ...except for jwertyCode.jwertyCombo...
- if (p !== 'jwertyCombo' && event[p] != jwertyCode[n][p]) returnValue = false;
- }
- // If this jwertyCode optional wasn't falsey, then we can return early.
- if (returnValue !== false) return returnValue;
- }
- return returnValue;
- },
- /**
- * jwerty.key
- *
- * `jwerty.key` will attach an event listener and fire
- * `callbackFunction` when `jwertyCode` matches. The event listener is
- * attached to `document`, meaning it will listen for any key events
- * on the page (a global shortcut listener). If `callbackContext` is
- * specified then it will be supplied as `callbackFunction`'s context
- * - in other words, the keyword `this` will be set to
- * `callbackContext` inside the `callbackFunction` function.
- * returns a subscription handle `h`, by which you may undo the binding
- * by calling `h.unbind()`
- *
- * @param {Mixed} jwertyCode can be an array, or string of key
- * combinations, which includes optinals and or sequences
- * @param {Function} callbackFunction is a function (or boolean) which
- * is fired when jwertyCode is matched. Return false to
- * preventDefault()
- * @param {Object} callbackContext (Optional) The context to call
- * `callback` with (i.e this)
- * @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
- * or an HTML*Element on which to bind the eventListener
- * @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
- * object, or an HTML*Element on which to scope the selector
- *
- */
- key: function (jwertyCode, callbackFunction, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */) {
- // Because callbackContext is optional, we should check if the
- // `callbackContext` is a string or element, and if it is, then the
- // function was called without a context, and `callbackContext` is
- // actually `selector`
- var realSelector = realTypeOf(callbackContext, 'element') || realTypeOf(callbackContext, 'string') ? callbackContext : selector,
- // If `callbackContext` is undefined, or if we skipped it (and
- // therefore it is `realSelector`), set context to `global`.
- realcallbackContext = realSelector === callbackContext ? global : callbackContext,
- // Finally if we did skip `callbackContext`, then shift
- // `selectorContext` to the left (take it from `selector`)
- realSelectorContext = realSelector === callbackContext ? selector : selectorContext;
- // If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
- // then just use it neat, otherwise find it in DOM using $$()
- var element = realTypeOf(realSelector, 'element') ? realSelector : $$(realSelector, realSelectorContext);
- var callback = jwerty.event(jwertyCode, callbackFunction, realcallbackContext);
- $b( element, callback );
- return {unbind:function(){ $u( element, callback ) }};
- },
- /**
- * jwerty.fire
- *
- * `jwerty.fire` will construct a keyup event to fire, based on
- * `jwertyCode`. The event will be fired against `selector`.
- * `selectorContext` is used to search for `selector` within
- * `selectorContext`, similar to jQuery's
- * `$('selector', 'context')`.
- *
- * @param {Mixed} jwertyCode can be an array, or string of key
- * combinations, which includes optinals and or sequences
- * @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
- * or an HTML*Element on which to bind the eventListener
- * @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
- * object, or an HTML*Element on which to scope the selector
- *
- */
- fire: function (jwertyCode, selector /*? document */, selectorContext /*? body */, i) {
- jwertyCode = new JwertyCode(jwertyCode);
- var realI = realTypeOf(selectorContext, 'number') ? selectorContext : i;
- // If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
- // then just use it neat, otherwise find it in DOM using $$()
- $f(
- realTypeOf(selector, 'element') ? selector : $$(selector, selectorContext),
- jwertyCode[realI || 0][0]
- );
- },
- KEYS: _keys
- };
- }(typeof global !== 'undefined' && global.window || this, (typeof module !== 'undefined' && module.exports ? module.exports : this)));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement