Advertisement
Guest User

Untitled

a guest
Aug 5th, 2017
861
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * jwerty - Awesome handling of keyboard events
  3.  *
  4.  * jwerty is a JS lib which allows you to bind, fire and assert key combination
  5.  * strings against elements and events. It normalises the poor std api into
  6.  * something easy to use and clear.
  7.  *
  8.  * This code is licensed under the MIT
  9.  * For the full license see: http://keithamus.mit-license.org/
  10.  * For more information see: http://keithamus.github.com/jwerty
  11.  *
  12.  * @author Keith Cirkel ('keithamus') <[email protected]>
  13.  * @license http://keithamus.mit-license.org/
  14.  * @copyright Copyright © 2011, Keith Cirkel
  15.  *
  16.  */
  17. (function (global, exports) {
  18.  
  19.     // Try require external librairies in Node.js context
  20.     function tryRequire(mod) {
  21.         if (typeof require == 'function' && typeof module !== 'undefined' && module.exports) {
  22.             try {
  23.                 return require(mod.toLowerCase());
  24.             } catch (err) {}
  25.         } else {
  26.             return global[mod];
  27.         }
  28.     }
  29.  
  30.     // Helper methods & vars:
  31.     var $d = global.document,
  32.         $ = (tryRequire('jQuery') || tryRequire('Zepto') || tryRequire('ender') || $d),
  33.         $$, // Element selector function
  34.         $b, // Event binding function
  35.         $u, // Event unbinding function
  36.         $f, // Event firing function
  37.         ke = 'keydown';
  38.  
  39.     function realTypeOf(v, s) {
  40.         return (v === null) ? s === 'null'
  41.         : (v === undefined) ? s === 'undefined'
  42.         : (v.is && v instanceof $) ? s === 'element'
  43.         : Object.prototype.toString.call(v).toLowerCase().indexOf(s) > 7;
  44.     }
  45.  
  46.     if ($ === $d) {
  47.         $$ = function (selector, context) {
  48.             return selector ? $.querySelector(selector, context || $) : $;
  49.         };
  50.         $b = function (e, fn) { e.addEventListener(ke, fn, false); };
  51.         $u = function (e, fn) { e.removeEventListener(ke, fn, false); };
  52.         $f = function (e, jwertyEv) {
  53.             var ret = $d.createEvent('Event'),
  54.             i;
  55.  
  56.             ret.initEvent(ke, true, true);
  57.  
  58.             for (i in jwertyEv) ret[i] = jwertyEv[i];
  59.  
  60.             return (e || $).dispatchEvent(ret);
  61.         };
  62.     } else {
  63.         $$ = function (selector, context) { return $(selector || $d, context); };
  64.         $b = function (e, fn) { $(e).bind(ke + '.jwerty', fn); };
  65.         $u = function (e, fn) { $(e).unbind(ke + '.jwerty', fn) };
  66.         $f = function (e, ob) { $(e || $d).trigger($.Event(ke, ob)); };
  67.     }
  68.  
  69.     // Private
  70.     var _modProps = { 16: 'shiftKey', 17: 'ctrlKey', 18: 'altKey', 91: 'metaKey' };
  71.  
  72.     // Generate key mappings for common keys that are not printable.
  73.     var _keys = {
  74.  
  75.         // MOD aka toggleable keys
  76.         mods: {
  77.             // Shift key, ?
  78.             '?': 16,
  79.             shift: 16,
  80.             // CTRL key, on Mac: ^
  81.             '^': 17,
  82.             ctrl: 17,
  83.             // ALT key, on Mac: ? (Alt)
  84.             '?': 18,
  85.             alt: 18,
  86.             option: 18,
  87.             // META, on Mac: ? (CMD), on Windows (Win), on Linux (Super)
  88.             '?': 91,
  89.             meta: 91,
  90.             cmd: 91,
  91.             'super': 91,
  92.             win: 91
  93.         },
  94.  
  95.         // Normal keys
  96.         keys: {
  97.             // Backspace key, on Mac: ? (Backspace)
  98.             '?': 8,
  99.             backspace: 8,
  100.             // Tab Key, on Mac: ? (Tab), on Windows ??
  101.             '?': 9,
  102.             '?': 9,
  103.             tab: 9,
  104.             // Return key, ?
  105.             '?': 13,
  106.             'return': 13,
  107.             enter: 13,
  108.             '?': 13,
  109.             // Pause/Break key
  110.             'pause': 19,
  111.             'pause-break': 19,
  112.             // Caps Lock key, ?
  113.             '?': 20,
  114.             caps: 20,
  115.             'caps-lock': 20,
  116.             // Escape key, on Mac: ?, on Windows: Esc
  117.             '?': 27,
  118.             escape: 27,
  119.             esc: 27,
  120.             // Space key
  121.             space: 32,
  122.             // Page-Up key, or pgup, on Mac: ?
  123.             '?': 33,
  124.             pgup: 33,
  125.             'page-up': 33,
  126.             // Page-Down key, or pgdown, on Mac: ?
  127.             '?': 34,
  128.             pgdown: 34,
  129.             'page-down': 34,
  130.             // END key, on Mac: ?
  131.             '?': 35,
  132.             end: 35,
  133.             // HOME key, on Mac: ?
  134.             '?': 36,
  135.             home: 36,
  136.             // Insert key, or ins
  137.             ins: 45,
  138.             insert: 45,
  139.             // Delete key, on Mac: ? (Delete)
  140.             del: 46,
  141.             'delete': 46,
  142.  
  143.             // Left Arrow Key, or ?
  144.             '?': 37,
  145.             left: 37,
  146.             'arrow-left': 37,
  147.             // Up Arrow Key, or ?
  148.             '?': 38,
  149.             up: 38,
  150.             'arrow-up': 38,
  151.             // Right Arrow Key, or ?
  152.             '?': 39,
  153.             right: 39,
  154.             'arrow-right': 39,
  155.             // Up Arrow Key, or ?
  156.             '?': 40,
  157.             down: 40,
  158.             'arrow-down': 40,
  159.  
  160.             // odities, printing characters that come out wrong:
  161.             // Num-Multiply, or *
  162.             '*': 106,
  163.             star: 106,
  164.             asterisk: 106,
  165.             multiply: 106,
  166.             // Num-Plus or +
  167.             '+': 107,
  168.             'plus': 107,
  169.             // Num-Subtract, or -
  170.             '-': 109,
  171.             subtract: 109,
  172.             'num-.': 110,
  173.             'num-period': 110,
  174.             'num-dot': 110,
  175.             'num-full-stop': 110,
  176.             'num-delete': 110,
  177.             // Semicolon
  178.             ';': 186,
  179.             semicolon: 186,
  180.             // = or equals
  181.             '=': 187,
  182.             'equals': 187,
  183.             // Comma, or ,
  184.             ',': 188,
  185.             comma: 188,
  186.             //'-': 189, //???
  187.             // Period, or ., or full-stop
  188.             '.': 190,
  189.             period: 190,
  190.             'full-stop': 190,
  191.             // Slash, or /, or forward-slash
  192.             '/': 191,
  193.             slash: 191,
  194.             'forward-slash': 191,
  195.             // Tick, or `, or back-quote
  196.             '`': 192,
  197.             tick: 192,
  198.             'back-quote': 192,
  199.             // Open bracket, or [
  200.             '[': 219,
  201.             'open-bracket': 219,
  202.             // Back slash, or \
  203.             '\\': 220,
  204.             'back-slash': 220,
  205.             // Close backet, or ]
  206.             ']': 221,
  207.             'close-bracket': 221,
  208.             // Apostraphe, or Quote, or '
  209.             '\'': 222,
  210.             quote: 222,
  211.             apostraphe: 222
  212.         }
  213.  
  214.     };
  215.  
  216.     // To minimise code bloat, add all of the 0-9 and NUMPAD 0-9 keys in a loop
  217.     var i = 47,
  218.         n = 0;
  219.     while (++i < 106) {
  220.         _keys.keys[n] = i;
  221.         _keys.keys['num-' + n] = i + 48;
  222.         ++n;
  223.     }
  224.  
  225.     // To minimise code bloat, add all of the F1-F25 keys in a loop
  226.     i = 111,
  227.     n = 1;
  228.     while (++i < 136) {
  229.         _keys.keys['f' + n] = i;
  230.         ++n;
  231.     }
  232.  
  233.     // To minimise code bloat, add all of the letters of the alphabet in a loop
  234.     i = 64;
  235.     while (++i < 91) {
  236.         _keys.keys[String.fromCharCode(i).toLowerCase()] = i;
  237.     }
  238.  
  239.     function JwertyCode(jwertyCode) {
  240.         var i,
  241.             c,
  242.             n,
  243.             z,
  244.             keyCombo,
  245.             optionals,
  246.             jwertyCodeFragment,
  247.             rangeMatches,
  248.             rangeI;
  249.  
  250.         // In-case we get called with an instance of ourselves, just return that.
  251.         if (jwertyCode instanceof JwertyCode) return jwertyCode;
  252.  
  253.         // If jwertyCode isn't an array, cast it as a string and split into array.
  254.         if (!realTypeOf(jwertyCode, 'array')) {
  255.             jwertyCode = (String(jwertyCode)).replace(/\s/g, '').toLowerCase()
  256.                 .match(/(?:\+,|[^,])+/g);
  257.         }
  258.  
  259.         // Loop through each key sequence in jwertyCode
  260.         for (i = 0, c = jwertyCode.length; i < c; ++i) {
  261.  
  262.             // If the key combo at this part of the sequence isn't an array,
  263.             // cast as a string and split into an array.
  264.             if (!realTypeOf(jwertyCode[i], 'array')) {
  265.                 jwertyCode[i] = String(jwertyCode[i])
  266.                     .match(/(?:\+\/|[^\/])+/g);
  267.             }
  268.  
  269.             // Parse the key optionals in this sequence
  270.             optionals = [],
  271.             n = jwertyCode[i].length;
  272.             while (n--) {
  273.  
  274.                 // Begin creating the object for this key combo
  275.                 jwertyCodeFragment = jwertyCode[i][n];
  276.  
  277.                 keyCombo = {
  278.                     jwertyCombo: String(jwertyCodeFragment),
  279.                     shiftKey: false,
  280.                     ctrlKey: false,
  281.                     altKey: false,
  282.                     metaKey: false
  283.                 };
  284.  
  285.                 // If jwertyCodeFragment isn't an array then cast as a string
  286.                 // and split it into one.
  287.                 if (!realTypeOf(jwertyCodeFragment, 'array')) {
  288.                     jwertyCodeFragment = String(jwertyCodeFragment).toLowerCase()
  289.                         .match(/(?:(?:[^\+])+|\+\+|^\+$)/g);
  290.                 }
  291.  
  292.                 z = jwertyCodeFragment.length;
  293.                 while (z--) {
  294.  
  295.                     // Normalise matching errors
  296.                     if (jwertyCodeFragment[z] === '++') jwertyCodeFragment[z] = '+';
  297.  
  298.                     // Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
  299.                     if (jwertyCodeFragment[z] in _keys.mods) {
  300.                         keyCombo[_modProps[_keys.mods[jwertyCodeFragment[z]]]] = true;
  301.                     } else if (jwertyCodeFragment[z] in _keys.keys) {
  302.                         keyCombo.keyCode = _keys.keys[jwertyCodeFragment[z]];
  303.                     } else {
  304.                         rangeMatches = jwertyCodeFragment[z].match(/^\[([^-]+\-?[^-]*)-([^-]+\-?[^-]*)\]$/);
  305.                     }
  306.                 }
  307.                 if (realTypeOf(keyCombo.keyCode, 'undefined')) {
  308.                     // If we picked up a range match earlier...
  309.                     if (rangeMatches && (rangeMatches[1] in _keys.keys) && (rangeMatches[2] in _keys.keys)) {
  310.                         rangeMatches[2] = _keys.keys[rangeMatches[2]];
  311.                         rangeMatches[1] = _keys.keys[rangeMatches[1]];
  312.  
  313.                         // Go from match 1 and capture all key-comobs up to match 2
  314.                         for (rangeI = rangeMatches[1]; rangeI < rangeMatches[2]; ++rangeI) {
  315.                             optionals.push({
  316.                                 altKey: keyCombo.altKey,
  317.                                 shiftKey: keyCombo.shiftKey,
  318.                                 metaKey: keyCombo.metaKey,
  319.                                 ctrlKey: keyCombo.ctrlKey,
  320.                                 keyCode: rangeI,
  321.                                 jwertyCombo: String(jwertyCodeFragment)
  322.                             });
  323.  
  324.                         }
  325.                         keyCombo.keyCode = rangeI;
  326.                     // Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
  327.                     } else {
  328.                         keyCombo.keyCode = 0;
  329.                     }
  330.                 }
  331.                 optionals.push(keyCombo);
  332.  
  333.             }
  334.             this[i] = optionals;
  335.         }
  336.         this.length = i;
  337.         return this;
  338.     }
  339.  
  340.     var jwerty = exports.jwerty = {
  341.         /**
  342.          * jwerty.event
  343.          *
  344.          * `jwerty.event` will return a function, which expects the first
  345.          *  argument to be a key event. When the key event matches `jwertyCode`,
  346.          *  `callbackFunction` is fired. `jwerty.event` is used by `jwerty.key`
  347.          *  to bind the function it returns. `jwerty.event` is useful for
  348.          *  attaching to your own event listeners. It can be used as a decorator
  349.          *  method to encapsulate functionality that you only want to fire after
  350.          *  a specific key combo. If `callbackContext` is specified then it will
  351.          *  be supplied as `callbackFunction`'s context - in other words, the
  352.          *  keyword `this` will be set to `callbackContext` inside the
  353.          *  `callbackFunction` function.
  354.          *
  355.          *   @param {Mixed} jwertyCode can be an array, or string of key
  356.          *      combinations, which includes optinals and or sequences
  357.          *   @param {Function} callbackFucntion is a function (or boolean) which
  358.          *      is fired when jwertyCode is matched. Return false to
  359.          *      preventDefault()
  360.          *   @param {Object} callbackContext (Optional) The context to call
  361.          *      `callback` with (i.e this)
  362.          *
  363.          */
  364.         event: function (jwertyCode, callbackFunction, callbackContext /*? this */) {
  365.  
  366.             // Construct a function out of callbackFunction, if it is a boolean.
  367.             if (realTypeOf(callbackFunction, 'boolean')) {
  368.                 var bool = callbackFunction;
  369.                 callbackFunction = function () { return bool; };
  370.             }
  371.  
  372.             jwertyCode = new JwertyCode(jwertyCode);
  373.  
  374.             // Initialise in-scope vars.
  375.             var i = 0,
  376.                 c = jwertyCode.length - 1,
  377.                 returnValue,
  378.                 jwertyCodeIs;
  379.  
  380.             // This is the event listener function that gets returned...
  381.             return function (event) {
  382.  
  383.                 // if jwertyCodeIs returns truthy (string)...
  384.                 if ((jwertyCodeIs = jwerty.is(jwertyCode, event, i))) {
  385.                     // ... and this isn't the last key in the sequence,
  386.                     // incriment the key in sequence to check.
  387.                     if (i < c) {
  388.                         ++i;
  389.                         return;
  390.                     // ... and this is the last in the sequence (or the only
  391.                     // one in sequence), then fire the callback
  392.                     } else {
  393.                         returnValue = callbackFunction.call(
  394.                             callbackContext || this, event, jwertyCodeIs);
  395.  
  396.                         // If the callback returned false, then we should run
  397.                         // preventDefault();
  398.                         if (returnValue === false) event.preventDefault();
  399.  
  400.                         // Reset i for the next sequence to fire.
  401.                         i = 0;
  402.                         return;
  403.                     }
  404.                 }
  405.  
  406.                 // If the event didn't hit this time, we should reset i to 0,
  407.                 // that is, unless this combo was the first in the sequence,
  408.                 // in which case we should reset i to 1.
  409.                 i = jwerty.is(jwertyCode, event) ? 1 : 0;
  410.             };
  411.         },
  412.  
  413.         /**
  414.          * jwerty.is
  415.          *
  416.          * `jwerty.is` will return a boolean value, based on if `event` matches
  417.          *  `jwertyCode`. `jwerty.is` is called by `jwerty.event` to check
  418.          *  whether or not to fire the callback. `event` can be a DOM event, or
  419.          *  a jQuery/Zepto/Ender manufactured event. The properties of
  420.          *  `jwertyCode` (speficially ctrlKey, altKey, metaKey, shiftKey and
  421.          *  keyCode) should match `jwertyCode`'s properties - if they do, then
  422.          *  `jwerty.is` will return `true`. If they don't, `jwerty.is` will
  423.          *  return `false`.
  424.          *
  425.          *   @param {Mixed} jwertyCode can be an array, or string of key
  426.          *      combinations, which includes optinals and or sequences
  427.          *   @param {KeyboardEvent} event is the KeyboardEvent to assert against
  428.          *   @param {Integer} i (Optional) checks the `i` key in jwertyCode
  429.          *      sequence
  430.          *
  431.          */
  432.         is: function (jwertyCode, event, i /*? 0*/) {
  433.             jwertyCode = new JwertyCode(jwertyCode);
  434.             // Default `i` to 0
  435.             i = i || 0;
  436.             // We are only interested in `i` of jwertyCode;
  437.             jwertyCode = jwertyCode[i];
  438.             // jQuery stores the *real* event in `originalEvent`, which we use
  439.             // because it does annoything stuff to `metaKey`
  440.             event = event.originalEvent || event;
  441.  
  442.             // We'll look at each optional in this jwertyCode sequence...
  443.             var n = jwertyCode.length,
  444.                 returnValue = false;
  445.  
  446.             // Loop through each fragment of jwertyCode
  447.             while (n--) {
  448.                 returnValue = jwertyCode[n].jwertyCombo;
  449.                 // For each property in the jwertyCode object, compare to `event`
  450.                 for (var p in jwertyCode[n]) {
  451.                     // ...except for jwertyCode.jwertyCombo...
  452.                     if (p !== 'jwertyCombo' && event[p] != jwertyCode[n][p]) returnValue = false;
  453.                 }
  454.                 // If this jwertyCode optional wasn't falsey, then we can return early.
  455.                 if (returnValue !== false) return returnValue;
  456.             }
  457.             return returnValue;
  458.         },
  459.  
  460.         /**
  461.          * jwerty.key
  462.          *
  463.          *  `jwerty.key` will attach an event listener and fire
  464.          *   `callbackFunction` when `jwertyCode` matches. The event listener is
  465.          *   attached to `document`, meaning it will listen for any key events
  466.          *   on the page (a global shortcut listener). If `callbackContext` is
  467.          *   specified then it will be supplied as `callbackFunction`'s context
  468.          *   - in other words, the keyword `this` will be set to
  469.          *   `callbackContext` inside the `callbackFunction` function.
  470.          *   returns a subscription handle `h`, by which you may undo the binding
  471.          *   by calling `h.unbind()`
  472.          *
  473.          *   @param {Mixed} jwertyCode can be an array, or string of key
  474.          *      combinations, which includes optinals and or sequences
  475.          *   @param {Function} callbackFunction is a function (or boolean) which
  476.          *      is fired when jwertyCode is matched. Return false to
  477.          *      preventDefault()
  478.          *   @param {Object} callbackContext (Optional) The context to call
  479.          *      `callback` with (i.e this)
  480.          *   @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
  481.          *      or an HTML*Element on which to bind the eventListener
  482.          *   @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
  483.          *      object, or an HTML*Element on which to scope the selector
  484.          *
  485.          */
  486.         key: function (jwertyCode, callbackFunction, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */) {
  487.             // Because callbackContext is optional, we should check if the
  488.             // `callbackContext` is a string or element, and if it is, then the
  489.             // function was called without a context, and `callbackContext` is
  490.             // actually `selector`
  491.             var realSelector = realTypeOf(callbackContext, 'element') || realTypeOf(callbackContext, 'string') ? callbackContext : selector,
  492.             // If `callbackContext` is undefined, or if we skipped it (and
  493.             // therefore it is `realSelector`), set context to `global`.
  494.                 realcallbackContext = realSelector === callbackContext ? global : callbackContext,
  495.             // Finally if we did skip `callbackContext`, then shift
  496.             // `selectorContext` to the left (take it from `selector`)
  497.                 realSelectorContext = realSelector === callbackContext ? selector : selectorContext;
  498.  
  499.             // If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
  500.             // then just use it neat, otherwise find it in DOM using $$()
  501.             var element = realTypeOf(realSelector, 'element') ? realSelector : $$(realSelector, realSelectorContext);
  502.             var callback = jwerty.event(jwertyCode, callbackFunction, realcallbackContext);
  503.             $b( element, callback );
  504.            
  505.             return {unbind:function(){ $u( element, callback ) }};
  506.         },
  507.        
  508.         /**
  509.          * jwerty.fire
  510.          *
  511.          * `jwerty.fire` will construct a keyup event to fire, based on
  512.          *  `jwertyCode`. The event will be fired against `selector`.
  513.          *  `selectorContext` is used to search for `selector` within
  514.          *  `selectorContext`, similar to jQuery's
  515.          *  `$('selector', 'context')`.
  516.          *
  517.          *   @param {Mixed} jwertyCode can be an array, or string of key
  518.          *      combinations, which includes optinals and or sequences
  519.          *   @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
  520.          *      or an HTML*Element on which to bind the eventListener
  521.          *   @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
  522.          *      object, or an HTML*Element on which to scope the selector
  523.          *
  524.          */
  525.         fire: function (jwertyCode, selector /*? document */, selectorContext /*? body */, i) {
  526.             jwertyCode = new JwertyCode(jwertyCode);
  527.             var realI = realTypeOf(selectorContext, 'number') ? selectorContext : i;
  528.  
  529.             // If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
  530.             // then just use it neat, otherwise find it in DOM using $$()
  531.             $f(
  532.                 realTypeOf(selector, 'element') ? selector : $$(selector, selectorContext),
  533.                 jwertyCode[realI || 0][0]
  534.             );
  535.         },
  536.  
  537.         KEYS: _keys
  538.     };
  539.  
  540. }(typeof global !== 'undefined' && global.window || this, (typeof module !== 'undefined' && module.exports ? module.exports : this)));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement