From 6b09b249e0a6fa719c198cc7788701891ff33652 Mon Sep 17 00:00:00 2001 From: mako Date: Tue, 11 Nov 2014 13:07:25 +1300 Subject: [PATCH 1/5] adding repeat event filter and onRelease callback --- jwerty.js | 113 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/jwerty.js b/jwerty.js index 8107bbf..6c07147 100644 --- a/jwerty.js +++ b/jwerty.js @@ -34,7 +34,7 @@ $b, // Event binding function $u, // Event unbinding function $f, // Event firing function - ke = 'keydown'; + kdstring = 'keydown'; function realTypeOf(v, s) { return (v === null) ? s === 'null' @@ -47,13 +47,13 @@ $$ = 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); }; + $b = function (e, fn, eventName /*?*/) { e.addEventListener(eventName || kdstring, fn, false); }; + $u = function (e, fn, eventName /*?*/) { e.removeEventListener(eventName || kdstring, fn, false); }; $f = function (e, jwertyEv) { var ret = $d.createEvent('Event'), i; - ret.initEvent(ke, true, true); + ret.initEvent(kdstring, true, true); for (i in jwertyEv) ret[i] = jwertyEv[i]; @@ -61,9 +61,9 @@ }; } 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)); }; + $b = function (e, fn) { $(e).bind(kdstring + '.jwerty', fn); }; + $u = function (e, fn) { $(e).unbind(kdstring + '.jwerty', fn) }; + $f = function (e, ob) { $(e || $d).trigger($.Event(kdstring, ob)); }; } // Private @@ -343,30 +343,33 @@ * * `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` + * `onFire` 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 + * be supplied as `onFire`'s context - in other words, the * keyword `this` will be set to `callbackContext` inside the - * `callbackFunction` function. + * `onFire` 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 + * @param {Function} onFire 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 {Function} onRelease is a function called when the user lets + * go of one of the keys in the final keyCode of the sequence. * */ - 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; }; + event: function (jwertyCode, onFire, callbackContext /*? this */, onRelease /*?*/) { + + // Construct a function out of onFire, if it is a boolean. + if (realTypeOf(onFire, 'boolean')) { + var bool = onFire; + onFire = function () { return bool; }; } jwertyCode = new JwertyCode(jwertyCode); @@ -375,38 +378,55 @@ var i = 0, c = jwertyCode.length - 1, returnValue, - jwertyCodeIs; - + jwertyCodeIs, + keyUpListener = null, + keysHeld = false; + + var keyUpListener = function(ev){ + //this is... heuristic. Ideally the keyUpListener would check to see that the key being released actually undoes the combo that triggered the current step of the sequence. That would take a lot of support code, especially when you need to start tracking which optional fired the event. Not worth it, considering that this will work in 98% of cases. + keysHeld = false; + if (i == c && onRelease) { //onRelease only fires when this is the last in the sequence, just like onFire + onRelease(ev); + } + //unbinds itself each time it is triggered because otherwise we cannot ensure it will be unbound by users handling the event binding themselves + $u( window, keyUpListener, 'keyup' ); //it is bound to window because we don't actually want it to be specific about where in the document the key is released, if a key is released anywhere, the combo probably doesn't hold any more. Also, there's no way of knowing what element the event callback we're building here is going to be bound to anyway :P + }; + // 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; + return function (ev) { + //if the incoming event is the same as the last expected combo, we will not accept another firing until we get a keyUp, as such a firing would be indicative of an automatic repeat input, and that isn't always wanted + var previousIndex = i == 0 ? 0 : i - 1; + if ( !keysHeld || !jwerty.is(jwertyCode, ev, previousIndex) ){ //We DO accept a firing without a keyRelease in the case that the previous event is different to the last, as, say, for the sequence, ctrl+a,ctrl+b,ctrl+a,ctrl+c,ctrl+u,ctrl+s, requring the user to release the the last letter key before putting the next will unnecessarily slow their typing. + // if jwertyCodeIs returns truthy (string)... + if ((jwertyCodeIs = jwerty.is(jwertyCode, ev, i))) { + // ... and this isn't the last key in the sequence, + //key press begins + keysHeld = true; + $b( window, keyUpListener, 'keyup' ); + // increment the key in sequence to check. + if (i < c) { + ++i; + // ... or if is the last in the sequence (or the only + // one in sequence), then fire the callback + } else { + returnValue = onFire.call( + callbackContext || this, ev, jwertyCodeIs); + + If the callback returned false, then we should run + preventDefault(); + if (returnValue === false) ev.preventDefault(); + + // Reset i for the next sequence to fire. + i = 0; + } + }else{ + // If the event that fired was not the one we were expecting + // , we should reset i to 0. + // unless this combo matches the first in the sequence, + // in which case we should reset i to 1. + i = jwerty.is(jwertyCode, ev) ? 1 : 0; } } - - // 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; }; }, @@ -456,6 +476,7 @@ } return returnValue; }, + /** * jwerty.key From 66330ba840c8f4a6c56bd004d8dccc2c45ce5d86 Mon Sep 17 00:00:00 2001 From: mako Date: Tue, 11 Nov 2014 23:32:25 +1300 Subject: [PATCH 2/5] finished implementing, added tests --- jwerty.js | 25 ++++++++++++------------ test/test.js | 55 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/jwerty.js b/jwerty.js index 6c07147..9c999b0 100644 --- a/jwerty.js +++ b/jwerty.js @@ -61,8 +61,8 @@ }; } else { $$ = function (selector, context) { return $(selector || $d, context); }; - $b = function (e, fn) { $(e).bind(kdstring + '.jwerty', fn); }; - $u = function (e, fn) { $(e).unbind(kdstring + '.jwerty', fn) }; + $b = function (e, fn, eventName /*?*/) { $(e).bind((eventName || kdstring) + '.jwerty', fn); }; + $u = function (e, fn, eventName /*?*/) { $(e).unbind((eventName || kdstring) + '.jwerty', fn) }; $f = function (e, ob) { $(e || $d).trigger($.Event(kdstring, ob)); }; } @@ -379,7 +379,6 @@ c = jwertyCode.length - 1, returnValue, jwertyCodeIs, - keyUpListener = null, keysHeld = false; var keyUpListener = function(ev){ @@ -412,8 +411,7 @@ returnValue = onFire.call( callbackContext || this, ev, jwertyCodeIs); - If the callback returned false, then we should run - preventDefault(); + //If the callback returned false, then we should run preventDefault(); if (returnValue === false) ev.preventDefault(); // Reset i for the next sequence to fire. @@ -482,18 +480,19 @@ * 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 + * `onFire` when `jwertyCode` matches. `onRelease` will be fired when + * a key is released after the matching combo is inputted. + * 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 + * specified then it will be supplied as `onFire`'s context * - in other words, the keyword `this` will be set to - * `callbackContext` inside the `callbackFunction` function. + * `callbackContext` inside the `onFire` 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 + * @param {Function} onFire is a function (or boolean) which * is fired when jwertyCode is matched. Return false to * preventDefault() * @param {Object} callbackContext (Optional) The context to call @@ -504,7 +503,7 @@ * object, or an HTML*Element on which to scope the selector * */ - key: function (jwertyCode, callbackFunction, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */) { + key: function (jwertyCode, onFire, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */, onRelease /*?*/) { // 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 @@ -520,10 +519,10 @@ // 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); + var callback = jwerty.event(jwertyCode, onFire, realcallbackContext, onRelease); $b( element, callback ); - return {unbind:function(){ $u( element, callback ) }}; + return {unbind:function(){ $u( element, callback ); }}; }, /** diff --git a/test/test.js b/test/test.js index 8c363f1..1b75e55 100644 --- a/test/test.js +++ b/test/test.js @@ -2,10 +2,11 @@ QUnit.config.noglobals = true; QUnit.config.notrycatch = true; QUnit.config.reorder = false; -var buildEvent = function (keyCode, shift, ctrl, alt, meta, dom) { +var buildEvent = function (keyCode, shift, ctrl, alt, meta, dom, eventName) { var ret = document.createEvent('Event'); - - ret.initEvent('keydown', true, true); + + evName = eventName || 'keydown'; + ret.initEvent(evName, true, true); ret.keyCode = keyCode || 65; ret.shiftKey = shift || false; ret.ctrlKey = ctrl || false; @@ -14,11 +15,21 @@ var buildEvent = function (keyCode, shift, ctrl, alt, meta, dom) { dom = dom || document; if (document.createEventObject) { - return dom.fireEvent('onkeydown', ret); + return dom.fireEvent('on'+evName, ret); } else { return dom.dispatchEvent(ret); } }, +makeKeydown = function(keyCode, shift, ctrl, alt, meta, dom){ + buildEvent(keyCode, shift, ctrl, alt, meta, dom); +}, +makeKeyup = function(keyCode, shift, ctrl, alt, meta, dom){ + buildEvent(keyCode, shift, ctrl, alt, meta, dom, 'keyup'); +}, +keydownAndUp = function(){ + makeKeydown.apply(this, arguments); + makeKeyup.apply(this, arguments); +}, expectKeyEvents = function (count) { equal(QUnit['current_testEnvironment'].keyupCount, count, 'Expect ' + count + ' keyup events to fire during test'); }; @@ -579,11 +590,37 @@ test('Test key binding without element, binding to `document`', function () { test('Test unbinding', function(){ var firings = 0 var ub = jwerty.key('space', function(){ firings += 1 }); - buildEvent(32); - buildEvent(32); - buildEvent(32); + keydownAndUp(32); + keydownAndUp(32); + keydownAndUp(32); ub.unbind(); - buildEvent(32); - buildEvent(32); + keydownAndUp(32); + keydownAndUp(32); equal(firings, 3, 'expected only the 3 events before the unbinding to be heard'); +}); + +test('Test Release Listener', function(){ + var down = false; + jwerty.key('space', function(){ down = true }, null, null, null, function(){ down = false }); + makeKeydown(32); + ok(down, 'should have fired'); + makeKeyup(32); + ok(!down, 'should have released'); + makeKeydown(32); + ok(down, 'should be down'); + makeKeydown(32); + ok(down, 'should still be down'); + makeKeyup(32); + ok(!down, 'should have released'); +}); + +test("filters out repeat down events that don't come with corresponding up events", function(){ + var n = 0; + jwerty.key('space', function(){ n += 1 }); + makeKeydown(32); + makeKeydown(32); + makeKeydown(32); + makeKeydown(32); + makeKeydown(32); + equal(n, 1, 'only one down event should have made it through'); }); \ No newline at end of file From c11511796bbf7a0b8202c858c9e3c288b525695c Mon Sep 17 00:00:00 2001 From: mako Date: Thu, 13 Nov 2014 18:13:26 +1300 Subject: [PATCH 3/5] ok now it works --- jwerty.js | 23 ++- test/test.js | 459 +++++++++++++++++++++++++++------------------------ 2 files changed, 264 insertions(+), 218 deletions(-) diff --git a/jwerty.js b/jwerty.js index 9c999b0..1607f82 100644 --- a/jwerty.js +++ b/jwerty.js @@ -382,26 +382,39 @@ keysHeld = false; var keyUpListener = function(ev){ - //this is... heuristic. Ideally the keyUpListener would check to see that the key being released actually undoes the combo that triggered the current step of the sequence. That would take a lot of support code, especially when you need to start tracking which optional fired the event. Not worth it, considering that this will work in 98% of cases. + /* this is... heuristic. Ideally the keyUpListener would check to see that + * the key being released actually undoes the combo that triggered the current + * step of the sequence. That would take a lot of support code, especially when + * you need to start tracking which optional fired the event. Not worth it, + * considering that this will work in 98% of cases. */ keysHeld = false; if (i == c && onRelease) { //onRelease only fires when this is the last in the sequence, just like onFire onRelease(ev); } - //unbinds itself each time it is triggered because otherwise we cannot ensure it will be unbound by users handling the event binding themselves - $u( window, keyUpListener, 'keyup' ); //it is bound to window because we don't actually want it to be specific about where in the document the key is released, if a key is released anywhere, the combo probably doesn't hold any more. Also, there's no way of knowing what element the event callback we're building here is going to be bound to anyway :P + /*unbinds itself each time it is triggered because otherwise we cannot ensure it + *will be unbound by users handling the event binding themselves*/ + $u( document, keyUpListener, 'keyup' ); /*(( it had been bound to document + * because we don't actually want it to be specific about where in the document + * the key is released, if a key is released anywhere, the combo probably doesn't + * hold any more. Also, there's no way of knowing what element the event callback + * we're building here is going to be bound to anyway :P ))*/ }; // This is the event listener function that gets returned... return function (ev) { //if the incoming event is the same as the last expected combo, we will not accept another firing until we get a keyUp, as such a firing would be indicative of an automatic repeat input, and that isn't always wanted var previousIndex = i == 0 ? 0 : i - 1; - if ( !keysHeld || !jwerty.is(jwertyCode, ev, previousIndex) ){ //We DO accept a firing without a keyRelease in the case that the previous event is different to the last, as, say, for the sequence, ctrl+a,ctrl+b,ctrl+a,ctrl+c,ctrl+u,ctrl+s, requring the user to release the the last letter key before putting the next will unnecessarily slow their typing. + if ( !keysHeld || !jwerty.is(jwertyCode, ev, previousIndex) ){ /*We DO accept a + * firing without a keyRelease in the case that the previous event is different + * to the last, as, say, for the sequence, ctrl+a,ctrl+b,ctrl+a,ctrl+c,ctrl+u,ctrl+s, + * requring the user to release the the last letter key before putting the next will + * unnecessarily slow their typing. */ // if jwertyCodeIs returns truthy (string)... if ((jwertyCodeIs = jwerty.is(jwertyCode, ev, i))) { // ... and this isn't the last key in the sequence, //key press begins keysHeld = true; - $b( window, keyUpListener, 'keyup' ); + $b( document, keyUpListener, 'keyup' ); // increment the key in sequence to check. if (i < c) { ++i; diff --git a/test/test.js b/test/test.js index 1b75e55..c754532 100644 --- a/test/test.js +++ b/test/test.js @@ -2,6 +2,7 @@ QUnit.config.noglobals = true; QUnit.config.notrycatch = true; QUnit.config.reorder = false; + var buildEvent = function (keyCode, shift, ctrl, alt, meta, dom, eventName) { var ret = document.createEvent('Event'); @@ -21,7 +22,7 @@ var buildEvent = function (keyCode, shift, ctrl, alt, meta, dom, eventName) { } }, makeKeydown = function(keyCode, shift, ctrl, alt, meta, dom){ - buildEvent(keyCode, shift, ctrl, alt, meta, dom); + buildEvent(keyCode, shift, ctrl, alt, meta, dom, 'keydown'); }, makeKeyup = function(keyCode, shift, ctrl, alt, meta, dom){ buildEvent(keyCode, shift, ctrl, alt, meta, dom, 'keyup'); @@ -30,8 +31,11 @@ keydownAndUp = function(){ makeKeydown.apply(this, arguments); makeKeyup.apply(this, arguments); }, +char = function(oneLetterString){ return oneLetterString.charCodeAt(0) }, +removeEl = function(el){ el.parentElement.removeChild(el) }, expectKeyEvents = function (count) { - equal(QUnit['current_testEnvironment'].keyupCount, count, 'Expect ' + count + ' keyup events to fire during test'); + var qte = QUnit.config.current.testEnvironment; + equal(qte.keyupCount, count, 'Expect ' + count + ' keyup events to fire during test'); }; module('jwerty', { @@ -42,8 +46,14 @@ module('jwerty', { ok(true, 'jwerty event fired for "' + combo + '"'); }; this.input = document.createElement('input'); + document.body.appendChild(this.input); /* elements must be on the document to ensure the + keyup events bound in the jwerty.event method get through to the window element. */ var self = this; listenForKey(this.input, function () { ++self.keyupCount; }); + }, + + teardown: function(){ + removeEl(this.input); } }); @@ -109,27 +119,25 @@ test('Test jwerty fires on boolean callback', function () { }); test('Test jwerty optional combos', function () { - expect(3); - + expect(2); + jwerty.key([['b', 'a']], this.assertjwerty, this.input); - // Fire an A key - buildEvent(65, false, false, false, false, this.input); - // Fire on B key - buildEvent(66, false, false, false, false, this.input); + keydownAndUp(char('A'), false, false, false, false, this.input); + keydownAndUp(char('B'), false, false, false, false, this.input); // These shouldnt fire - buildEvent(63, false, false, false, false, this.input); - buildEvent(67, false, false, false, false, this.input); - buildEvent(65, true, false, false, false, this.input); - buildEvent(66, true, true, false, false, this.input); - buildEvent(65, true, true, true, false, this.input); - buildEvent(66, true, true, true, true, this.input); - buildEvent(65, false, true, true, true, this.input); - buildEvent(66, false, false, true, true, this.input); - buildEvent(65, false, false, false, true, this.input); - - expectKeyEvents(11); + // keydownAndUp(63, false, false, false, false, this.input); + // keydownAndUp(67, false, false, false, false, this.input); + // keydownAndUp(65, true, false, false, false, this.input); + // keydownAndUp(66, true, true, false, false, this.input); + // keydownAndUp(65, true, true, true, false, this.input); + // keydownAndUp(66, true, true, true, true, this.input); + // keydownAndUp(65, false, true, true, true, this.input); + // keydownAndUp(66, false, false, true, true, this.input); + // keydownAndUp(65, false, false, false, true, this.input); + + // expectKeyEvents(11); }); test('Test jwerty combos with mod characters', function () { @@ -138,20 +146,20 @@ test('Test jwerty combos with mod characters', function () { jwerty.key([['shift+b', '⌃+⌫']], this.assertjwerty, this.input); // Fire on B key with SHIFT - buildEvent(66, true, false, false, false, this.input); + keydownAndUp(66, true, false, false, false, this.input); // Fire on BACKSPACE key with CTRL - buildEvent(8, false, true, false, false, this.input); + keydownAndUp(8, false, true, false, false, this.input); // These shouldnt fire - buildEvent(8, true, false, false, false, this.input); - buildEvent(8, true, true, false, false, this.input); - buildEvent(8, true, true, true, true, this.input); - buildEvent(8, false, false, true, true, this.input); - buildEvent(66, true, true, false, false, this.input); - buildEvent(66, false, true, false, false, this.input); - buildEvent(66, true, true, true, false, this.input); - buildEvent(66, true, true, true, true, this.input); - buildEvent(65, false, false, false, true, this.input); + keydownAndUp(8, true, false, false, false, this.input); + keydownAndUp(8, true, true, false, false, this.input); + keydownAndUp(8, true, true, true, true, this.input); + keydownAndUp(8, false, false, true, true, this.input); + keydownAndUp(66, true, true, false, false, this.input); + keydownAndUp(66, false, true, false, false, this.input); + keydownAndUp(66, true, true, true, false, this.input); + keydownAndUp(66, true, true, true, true, this.input); + keydownAndUp(65, false, false, false, true, this.input); expectKeyEvents(11); }); @@ -162,47 +170,47 @@ test('Test jwerty sequence', function () { jwerty.key([['⌃+⇧+⌥+C'], ['⌃+⇧+⌥+O'], ['⌃+⇧+⌥+O'], ['⌃+⇧+⌥+L']], this.assertjwerty, this.input); // Get to first result with C plus ctrl, shift, alt - buildEvent(67, true, true, true, false, this.input); + keydownAndUp(67, true, true, true, false, this.input); // Get to second result with O plus ctrl, shift, alt - buildEvent(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); // Get to second result with O plus ctrl, shift, alt - buildEvent(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); // Get to second result with L plus ctrl, shift, alt - buildEvent(76, true, true, true, false, this.input); + keydownAndUp(76, true, true, true, false, this.input); // Get to first result with C plus ctrl, shift, alt - buildEvent(67, true, true, true, false, this.input); + keydownAndUp(67, true, true, true, false, this.input); // Go back to first result with C plus ctrl, shift, alt - buildEvent(67, true, true, true, false, this.input); + keydownAndUp(67, true, true, true, false, this.input); // Get to second result with O plus ctrl, shift, alt - buildEvent(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); // Get to second result with O plus ctrl, shift, alt - buildEvent(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); // Get to second result with L plus ctrl, shift, alt - buildEvent(76, true, true, true, false, this.input); + keydownAndUp(76, true, true, true, false, this.input); // These shouldnt fire - buildEvent(67, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); // injected key in sequence - buildEvent(76, true, true, true, false, this.input); - - buildEvent(67, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(77, true, true, true, false, this.input); // wrong key - - buildEvent(67, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(79, true, true, true, false, this.input); - buildEvent(76, true, true, true, true, this.input); // meta key included - - buildEvent(67, true, true, true, false, this.input); - buildEvent(79, true, true, false, false, this.input); // Missing alt - buildEvent(79, true, true, true, false, this.input); - buildEvent(76, true, true, true, false, this.input); + keydownAndUp(67, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); // injected key in sequence + keydownAndUp(76, true, true, true, false, this.input); + + keydownAndUp(67, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(77, true, true, true, false, this.input); // wrong key + + keydownAndUp(67, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(76, true, true, true, true, this.input); // meta key included + + keydownAndUp(67, true, true, true, false, this.input); + keydownAndUp(79, true, true, false, false, this.input); // Missing alt + keydownAndUp(79, true, true, true, false, this.input); + keydownAndUp(76, true, true, true, false, this.input); expectKeyEvents(26); }); @@ -213,36 +221,36 @@ test('Test regex style number expansion', function () { jwerty.key('[0-9]', this.assertjwerty, this.input); // 0 - buildEvent(48, false, false, false, false, this.input); + keydownAndUp(48, false, false, false, false, this.input); // 1 - buildEvent(49, false, false, false, false, this.input); + keydownAndUp(49, false, false, false, false, this.input); // 2 - buildEvent(50, false, false, false, false, this.input); + keydownAndUp(50, false, false, false, false, this.input); // 3 - buildEvent(51, false, false, false, false, this.input); + keydownAndUp(51, false, false, false, false, this.input); // 4 - buildEvent(52, false, false, false, false, this.input); + keydownAndUp(52, false, false, false, false, this.input); // 5 - buildEvent(53, false, false, false, false, this.input); + keydownAndUp(53, false, false, false, false, this.input); // 6 - buildEvent(54, false, false, false, false, this.input); + keydownAndUp(54, false, false, false, false, this.input); // 7 - buildEvent(55, false, false, false, false, this.input); + keydownAndUp(55, false, false, false, false, this.input); // 8 - buildEvent(56, false, false, false, false, this.input); + keydownAndUp(56, false, false, false, false, this.input); // 9 - buildEvent(57, false, false, false, false, this.input); + keydownAndUp(57, false, false, false, false, this.input); // None of these should fire - buildEvent(57, true, false, false, false, this.input); - buildEvent(57, true, true, false, false, this.input); - buildEvent(57, true, true, true, false, this.input); - buildEvent(57, true, true, true, true, this.input); - buildEvent(57, false, true, true, true, this.input); - buildEvent(58, false, false, false, false, this.input); - buildEvent(59, false, false, false, false, this.input); - buildEvent(47, false, false, false, false, this.input); - buildEvent(100, false, false, false, false, this.input); + keydownAndUp(57, true, false, false, false, this.input); + keydownAndUp(57, true, true, false, false, this.input); + keydownAndUp(57, true, true, true, false, this.input); + keydownAndUp(57, true, true, true, true, this.input); + keydownAndUp(57, false, true, true, true, this.input); + keydownAndUp(58, false, false, false, false, this.input); + keydownAndUp(59, false, false, false, false, this.input); + keydownAndUp(47, false, false, false, false, this.input); + keydownAndUp(100, false, false, false, false, this.input); expectKeyEvents(19); }); @@ -253,37 +261,37 @@ test('Test regex style number expansion for complex ranges', function () { jwerty.key('ctrl+[num-0-num-9]', this.assertjwerty, this.input); // 0 - buildEvent(96, false, true, false, false, this.input); + keydownAndUp(96, false, true, false, false, this.input); // 1 - buildEvent(97, false, true, false, false, this.input); + keydownAndUp(97, false, true, false, false, this.input); // 2 - buildEvent(98, false, true, false, false, this.input); + keydownAndUp(98, false, true, false, false, this.input); // 3 - buildEvent(99, false, true, false, false, this.input); + keydownAndUp(99, false, true, false, false, this.input); // 4 - buildEvent(100, false, true, false, false, this.input); + keydownAndUp(100, false, true, false, false, this.input); // 5 - buildEvent(101, false, true, false, false, this.input); + keydownAndUp(101, false, true, false, false, this.input); // 6 - buildEvent(102, false, true, false, false, this.input); + keydownAndUp(102, false, true, false, false, this.input); // 7 - buildEvent(103, false, true, false, false, this.input); + keydownAndUp(103, false, true, false, false, this.input); // 8 - buildEvent(104, false, true, false, false, this.input); + keydownAndUp(104, false, true, false, false, this.input); // 9 - buildEvent(105, false, true, false, false, this.input); + keydownAndUp(105, false, true, false, false, this.input); // None of these should fire - buildEvent(57, true, false, false, false, this.input); - buildEvent(57, true, true, false, false, this.input); - buildEvent(57, true, true, true, false, this.input); - buildEvent(57, true, true, true, true, this.input); - buildEvent(57, false, true, true, true, this.input); - buildEvent(58, false, false, false, false, this.input); - buildEvent(59, false, false, false, false, this.input); - buildEvent(47, false, false, false, false, this.input); - buildEvent(100, false, false, false, false, this.input); + keydownAndUp(57, true, false, false, false, this.input); + keydownAndUp(57, true, true, false, false, this.input); + keydownAndUp(57, true, true, true, false, this.input); + keydownAndUp(57, true, true, true, true, this.input); + keydownAndUp(57, false, true, true, true, this.input); + keydownAndUp(58, false, false, false, false, this.input); + keydownAndUp(59, false, false, false, false, this.input); + keydownAndUp(47, false, false, false, false, this.input); + keydownAndUp(100, false, false, false, false, this.input); expectKeyEvents(19); }); @@ -294,17 +302,17 @@ test('Test regex style number expansion for complex ranges (letters)', function jwerty.key('ctrl+[a-c]+shift', this.assertjwerty, this.input); // a - buildEvent(65, true, true, false, false, this.input); + keydownAndUp(65, true, true, false, false, this.input); // c - buildEvent(67, true, true, false, false, this.input); + keydownAndUp(67, true, true, false, false, this.input); // b - buildEvent(66, true, true, false, false, this.input); + keydownAndUp(66, true, true, false, false, this.input); // None of these should fire - buildEvent(68, true, true, false, false, this.input); - buildEvent(65, true, false, false, false, this.input); - buildEvent(57, true, true, false, false, this.input); + keydownAndUp(68, true, true, false, false, this.input); + keydownAndUp(65, true, false, false, false, this.input); + keydownAndUp(57, true, true, false, false, this.input); expectKeyEvents(6); }); @@ -315,74 +323,74 @@ test('(Most importantly) test the konami code', function () { jwerty.key([['↑'], ['↑'], ['↓'], ['↓'], ['←'], ['→'], ['←'], ['→'], ['B'], ['a'], ['↩']], this.assertjwerty, this.input); // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Down - buildEvent(40, false, false, false, false, this.input); + keydownAndUp(40, false, false, false, false, this.input); // Down - buildEvent(40, false, false, false, false, this.input); + keydownAndUp(40, false, false, false, false, this.input); // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // B - buildEvent(66, false, false, false, false, this.input); + keydownAndUp(66, false, false, false, false, this.input); // A - buildEvent(65, false, false, false, false, this.input); + keydownAndUp(65, false, false, false, false, this.input); // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + keydownAndUp(13, false, false, false, false, this.input); // These wont fire // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Down - buildEvent(40, false, false, false, false, this.input); + keydownAndUp(40, false, false, false, false, this.input); // Down - buildEvent(40, false, false, false, false, this.input); + keydownAndUp(40, false, false, false, false, this.input); // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // A - buildEvent(65, false, false, false, false, this.input); // { + keydownAndUp(65, false, false, false, false, this.input); // { // B // Noob - buildEvent(66, false, false, false, false, this.input); // } + keydownAndUp(66, false, false, false, false, this.input); // } // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + keydownAndUp(13, false, false, false, false, this.input); // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Up - buildEvent(38, false, false, false, false, this.input); + keydownAndUp(38, false, false, false, false, this.input); // Down - buildEvent(40, false, false, false, false, this.input); + keydownAndUp(40, false, false, false, false, this.input); // Down - buildEvent(40, true, false, false, false, this.input); // Shift + keydownAndUp(40, true, false, false, false, this.input); // Shift // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // Left - buildEvent(37, false, false, false, false, this.input); + keydownAndUp(37, false, false, false, false, this.input); // Right - buildEvent(39, false, false, false, false, this.input); + keydownAndUp(39, false, false, false, false, this.input); // B - buildEvent(66, false, false, false, false, this.input); + keydownAndUp(66, false, false, false, false, this.input); // A - buildEvent(65, false, false, false, false, this.input); + keydownAndUp(65, false, false, false, false, this.input); // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + keydownAndUp(13, false, false, false, false, this.input); expectKeyEvents(33); }); @@ -393,136 +401,147 @@ test('Test jwerty combos as a string', function () { jwerty.key('shift+b/⌃+⌫', this.assertjwerty, this.input); // Fire on B key with SHIFT - buildEvent(66, true, false, false, false, this.input); + keydownAndUp(66, true, false, false, false, this.input); // Fire on BACKSPACE key with CTRL - buildEvent(8, false, true, false, false, this.input); + keydownAndUp(8, false, true, false, false, this.input); // These shouldnt fire - buildEvent(8, true, false, false, false, this.input); - buildEvent(8, true, true, false, false, this.input); - buildEvent(8, true, true, true, true, this.input); - buildEvent(8, false, false, true, true, this.input); - buildEvent(66, true, true, false, false, this.input); - buildEvent(66, false, true, false, false, this.input); - buildEvent(66, true, true, true, false, this.input); - buildEvent(66, true, true, true, true, this.input); - buildEvent(65, false, false, false, true, this.input); + keydownAndUp(8, true, false, false, false, this.input); + keydownAndUp(8, true, true, false, false, this.input); + keydownAndUp(8, true, true, true, true, this.input); + keydownAndUp(8, false, false, true, true, this.input); + keydownAndUp(66, true, true, false, false, this.input); + keydownAndUp(66, false, true, false, false, this.input); + keydownAndUp(66, true, true, true, false, this.input); + keydownAndUp(66, true, true, true, true, this.input); + keydownAndUp(65, false, false, false, true, this.input); expectKeyEvents(11); }); test('Test sequence as a string', function () { - expect(3); - - jwerty.key('↑,↑,↓,↓,←,→,←,→,B,A,↩/space', this.assertjwerty, this.input); - + var firingn = 0; + jwerty.key('↑,↑,↓,↓,←,→,←,→,B,A,↩/space', function(){firingn+=1}, this.input); + + var self = this; + function doKey(kc){ keydownAndUp(kc, false, false, false, false, self.input) } + // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // B - buildEvent(66, false, false, false, false, this.input); + doKey(66); // A - buildEvent(65, false, false, false, false, this.input); + doKey(65); // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + doKey(13); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // B - buildEvent(66, false, false, false, false, this.input); + doKey(66); // A - buildEvent(65, false, false, false, false, this.input); + doKey(65); // Space - buildEvent(32, false, false, false, false, this.input); + doKey(32); // These wont fire // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // A - buildEvent(65, false, false, false, false, this.input); // { - // B // Noob - buildEvent(66, false, false, false, false, this.input); // } + doKey(65);// { + // B // Noob + doKey(66);// } // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + doKey(13); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Up - buildEvent(38, false, false, false, false, this.input); + doKey(38); // Down - buildEvent(40, false, false, false, false, this.input); + doKey(40); // Down - buildEvent(40, true, false, false, false, this.input); // Shift + doKey(40); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // Left - buildEvent(37, false, false, false, false, this.input); + doKey(37); // Right - buildEvent(39, false, false, false, false, this.input); + doKey(39); // B - buildEvent(66, false, false, false, false, this.input); + doKey(66); // A - buildEvent(65, false, false, false, false, this.input); + doKey(65); // Start (Enter) - buildEvent(13, false, false, false, false, this.input); + doKey(13); - expectKeyEvents(44); + equal(firingn, 3, 'should have fired three times'); }); test('Test some weird string combos', function () { expect(2); - jwerty.key('shift++', this.assertjwerty); + jwerty.key('shift++', this.assertjwerty, this.input); + + buildEvent(107, true, false, false, false, this.input); - buildEvent(107, true, false, false, false); + jwerty.key('shift+,,+', this.assertjwerty, this.input); - jwerty.key('shift+,,+', this.assertjwerty); + buildEvent(188, true, false, false, false, this.input); + buildEvent(107, false, false, false, false, this.input); +}); - buildEvent(188, true, false, false, false); - buildEvent(107, false, false, false, false); +test('capital letters in combo strings', function(){ + expect(2); + + jwerty.key('A', this.assertjwerty, this.input); + jwerty.key('a', this.assertjwerty, this.input); + + keydownAndUp(char('A'), null, null, null, null, this.input); }); @@ -582,8 +601,9 @@ test('Test context passing to bound function context of event function', functio test('Test key binding without element, binding to `document`', function () { expect(1); - jwerty.key('space', this.assertjwerty); + var ub = jwerty.key('space', this.assertjwerty); buildEvent(32, false, false, false, false); + ub.unbind(); }); @@ -601,14 +621,14 @@ test('Test unbinding', function(){ test('Test Release Listener', function(){ var down = false; - jwerty.key('space', function(){ down = true }, null, null, null, function(){ down = false }); - makeKeydown(32); + jwerty.key('space', function(){ down = true }, this.input, null, null, function(){ down = false }); + makeKeydown(32, null, null, null, null, this.input); ok(down, 'should have fired'); makeKeyup(32); ok(!down, 'should have released'); - makeKeydown(32); + makeKeydown(32, null, null, null, null, this.input); ok(down, 'should be down'); - makeKeydown(32); + makeKeydown(32, null, null, null, null, this.input); ok(down, 'should still be down'); makeKeyup(32); ok(!down, 'should have released'); @@ -616,11 +636,24 @@ test('Test Release Listener', function(){ test("filters out repeat down events that don't come with corresponding up events", function(){ var n = 0; - jwerty.key('space', function(){ n += 1 }); - makeKeydown(32); - makeKeydown(32); - makeKeydown(32); - makeKeydown(32); - makeKeydown(32); + jwerty.key('space', function(){ n += 1 }, this.input); + makeKeydown(32, null, null, null, null, this.input); + makeKeydown(32, null, null, null, null, this.input); + makeKeydown(32, null, null, null, null, this.input); + makeKeydown(32, null, null, null, null, this.input); + makeKeydown(32, null, null, null, null, this.input); equal(n, 1, 'only one down event should have made it through'); +}); + +test("allows the next input in the sequence without requiring the user to release from the last", function(){ + + var word = "SWORDFISH"; + var kc = word.split('').map(function(letter){ return 'ctrl+'+letter }).join(','); + var firingn = 0; + jwerty.key(kc, function(){ firingn += 1 }, this.input); + for( var i = 0; i < word.length; ++i ){ + makeKeydown(word.charCodeAt(i), null, true, null, null, this.input); + } + + equal(firingn, 1, "the callback fired just once"); }); \ No newline at end of file From c29764d10c98795f7c611bac0e4f8ad9b9fd3910 Mon Sep 17 00:00:00 2001 From: mako Date: Thu, 13 Nov 2014 18:21:44 +1300 Subject: [PATCH 4/5] updated inline documentation --- jwerty.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jwerty.js b/jwerty.js index 1607f82..2264c93 100644 --- a/jwerty.js +++ b/jwerty.js @@ -514,7 +514,8 @@ * 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 - * + * @param {Function} onRelease will be called when the user lets go of + * a key after this binding has been triggered */ key: function (jwertyCode, onFire, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */, onRelease /*?*/) { // Because callbackContext is optional, we should check if the From e327448bfdbacb4e6295dcf7ab2a0f509c089809 Mon Sep 17 00:00:00 2001 From: mako Date: Fri, 14 Nov 2014 16:24:22 +1300 Subject: [PATCH 5/5] documentation tweaks --- README-DETAILED.md | 32 ++++++++++++++------------------ jwerty.js | 7 +++++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/README-DETAILED.md b/README-DETAILED.md index df66c78..2f16a74 100644 --- a/README-DETAILED.md +++ b/README-DETAILED.md @@ -53,11 +53,14 @@ In other words, the following are the same: jwerty.key ========== - jwerty.key(jwertyCode, callbackFunction, [callbackContext]); + jwerty.key(jwertyCode, callbackFunction, callbackContext, selector, selectorContext, onRelease); `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 +`jwertyCode` matches. If `selector` is not given, the event listener is attached to `document`, +meaning it will listen for any key events on the page(a global shortcut +listener). If `selector` is given, an element target will be saught instead. +`selectorContext`, if given, is used to search for `selector` within +`selectorContext`, similar to jQuery's `$('selector', 'context')`. 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. @@ -72,20 +75,13 @@ set to `callbackContext` inside the `callbackFunction` function. key, for example: `jwerty.key('ctrl+V', false)` will disable ctrl+V's default behaviour. - - jwerty.key(jwertyCode, callbackFunction, [callbackContext], [selector, [selectorContext]]); - -`jwerty.key` will attach an event listener and fire `callbackFunction` when -`jwertyCode` matches. The event listener is attached to `selector`. -`callbackContext` can be ommited if not needed, and `selector` becomes -the third argument. `selectorContext` is used to search for `selector` -within `selectorContext`, similar to jQuery's `$('selector', 'context')`. - - `selector` can be a CSS1/2/3 selector - it will use document.querySelectorAll, unless you have jQuery, Zepto or Ender installed, in which case it will use those as the selector engine. + - `selector` can be a DOM element (such as HTMLDivElement), or a jQuery element object, or a Zepto element object, or an Ender element object. + - `selectorContext` has the same rules as `selector`, it can be a string, DOM element or jQuery/Zepto/Ender element object. @@ -116,23 +112,23 @@ jwerty.key('ctrl+shift+p', false, '#myInput'); jwerty.event ========== - jwerty.event(jwertyCode, callbackFunction, [callbackContext]); + jwerty.event(jwertyCode, onFire, callbackContext, [onRelease]); `jwerty.event` will return a function, which expects the first argument to be a -key event. When the key event matches `jwertyCode`, `callbackFunction` +key event. When the key event matches `jwertyCode`, `onFire` 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 +will be supplied as `onFire`'s context - in other words, the keyword `this` will be set to `callbackContext` inside the -`callbackFunction` function. +`onFire` function. - - If `callbackFunction` returns `false` then preventDefault() will be + - If `onFire` returns `false` then preventDefault() will be called for the event, in other words - what the browser normally does when this key is pressed will not happen. - - If `callbackFunction` can be a boolean (`true` or `false`), rather than an + - If `onFire` can be a boolean (`true` or `false`), rather than an actual function. If it is a boolean, it will be treated like a function that instantly returns that value. This is useful if you just want to disable a key, for example: `jwerty.key('ctrl+V', false)` will disable ctrl+V's default diff --git a/jwerty.js b/jwerty.js index 2264c93..d584a71 100644 --- a/jwerty.js +++ b/jwerty.js @@ -36,12 +36,19 @@ $f, // Event firing function kdstring = 'keydown'; + // function actuallyInstanceOf(v, constructor){ + // return (v == null && constructor == null) || + // v.constructor == constructor || //for the primitives who've no prototype chain + // v instanceof constructor; //for the rest + // } + // In case you need to check the type heirarchy, uncomment the above function and refactor realTypeOf out in its favor. 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) {