Hello Everyone, I figured I'd post this here before I release the official Github page for Combinator. I'll have you guys do some initial testing if you want. Combinator is a small Javascript library for Keyboard shortcuts, i.e. ( [ic]ctrl+c[/ic], [ic]ctrl+j[/ic], [ic]ctrl+shift+d[/ic]) anything that you want and you can tell the page to do what you want instead. There are a few libraries out there already but I figured I'd post mine since mine is more user friend in the sense and does have some advantages compared to others like Mousetrap.
Combinator is very easy to use and comes with a wrapped up keyCodes object for your using without Combinator initialised. I'll explain that below after Combinator.
So first why the name Combinator? Well shortcuts are a combination of keys pressed, and well this is like the terminator of shortcuts thus Combinator was born
So include this Javascript file.
Mark it all pages, or you can add it directly to the head of your document by editing the head template.
So basically you're done, but how do we listen to shortcuts or anything like that? Well there are eight properties to the combinator object, and below is the explanation to each.
[ic]combinator.register(shortcut,properties);[/ic]
The register property is a function to add new shortcuts, I prefer you to use this method but you can write out the list of them yourself using the [ic]combinator.cmd[/ic] property. So let's take a look at how to use the register property.
So the above is a simple register example. Some notes to understand, most of what you see is the default value except for the exec property. So if you want your command to work inside inputs (inputs, textareas, contenteditable elements) then you don't need to add it. Basically if the default is true for you, you don't need to add it at all. So let's go over each of these properties separately.
[ic] title [/ic] is the name of the command
[ic] exec [/ic] is the function to execute when the command is entered. It has one argument which is a sequence to listen for after. See example below to understand. The [ic]this[/ic] inside the scope of exec is the [ic]event[/ic] property of keydown.
[ic] input [/ic] default is true, meaning the commands will work inside any input element, textarea, or contenteditable element.
[ic] repeat [/ic] default is true, repeat means if we hold down the command it will repeat itself, if we want it to execute once instead of repeat set this to false. Though remember the user just has to lift their finger from the key and press it again and it'll work.
[ic] once [/ic] default is false, once is basically what it says. The command will only fire once no matter how many times the user presses it the command. You can reset this later if you want.
[ic] ignoreClass [/ic] default is null, if you want an element such as a textarea that you don't want the commands to work in then create a class such as "ignore-halt" and add it to the textarea, this is good because if you only want one textarea to not work and other inputs to work then this is how it is done.
[ic]combinator.listen(location);[/ic]
listen makes the location we set, default argument is document, listen for the commands. Later this will be useful when combinator allows for multiple instances of combinator. If you want the entire document to listen then just leave the argument alone.
[ic]combinator.fetch(shortcut);[/ic]
fetch gets the object we want from our [ic]combinator.cmd[/ic] object, this is just useful so we don't have to write [ic]combinator.cmd["ctrl+b"][/ic] especially since I made cases all lower case and if you think that your command is actually [ic]ctrl+B[/ic] it will actually be [ic]ctrl+b[/ic]
[ic]combinator.release(shortcut);[/ic]
Release, this is a special function since we don't allow you to overwrite combinators you must first release the original from the object. This is incase you forget that you already defined the combinator.
Returns true if the object is released.
[ic]combinator.trigger(shortcut);[/ic]
trigger makes it so you can trigger the [ic]exec[/ic] function that you defined without having to press the commands.
[ic]combinator.record(callback,location);[/ic]
record the event on keydown happening in a specific location, default location is document again. This is great to use if you want to just listen to what keys were pressed. Example later on. Save this in a variable because it returns a function to stop listening.
[ic]combinator.cmd[/ic]
The cmd object is all the commands you've entered in.
Examples:
This combinator will work as follows. So first we press [ic]ctrl+b[/ic] now we wait and if we press [ic]a[/ic] the message that is logged will be "A was pressed".
Remember that if a new combinator is started that the old sequence that was waiting will be destroyed.
What this does is if we press "enter" it will log "Enter is 13 in a numerical key". Then the recorder will stop.
A complex example:
This will actually be a combinator that runs only once, until we press "a" then it will reset the combinator and we can start over again.
If you'd like any more examples please give me an idea and I'll write an example for you.
Combinator is very easy to use and comes with a wrapped up keyCodes object for your using without Combinator initialised. I'll explain that below after Combinator.
So first why the name Combinator? Well shortcuts are a combination of keys pressed, and well this is like the terminator of shortcuts thus Combinator was born
So include this Javascript file.
- Code:
[panda=js]
!function(){"use strict";function e(e,t,n){"addEventListener"in document?e.addEventListener(t,n):"attachEvent"in document?e.attachEvent("on"+t,n):e["on"+t]=n}function t(e,t,n){"addEventListener"in document?e.removeEventListener(t,n):"detachEvent"in document?e.attachEvent("on"+t,n):e["on"+t]=null}function n(e,n,r){r||(r=document);var i=this;addListener(r,"keydown",function a(o){i.waiting?keyCodes.literal[e.toUpperCase()]===o.which&&(n.call(i,o),i.waiting=!1,t(this,"keydown",a)):t(r,"keydown",a)})}function r(e){for(var t in y.cmd)e.call(y.cmd,t)}function i(t){t||(t=document),e(t,"keydown",function(e){var t,i,a,o,c;if(t="which"in e?e.which:e.keyCode,e.ctrlKey&&-1===p.indexOf("ctrl")&&p.push("ctrl"),e.altKey&&-1===p.indexOf("alt")&&p.push("alt"),e.shiftKey&&-1===p.indexOf("shift")&&p.push("shift"),p.length>0&&(g=!0,r(function(e){this[e].hasOwnProperty("waiting")&&delete this[e].waiting})),i=keyCodes.numerical[t],-1===p.indexOf(i)&&p.push(i),a=p.join("+").toLowerCase(),y.cmd.hasOwnProperty(a)){if(o=y.cmd[a],e.preventDefault(),e.stopPropagation(),!o.repeat&&o.executed||o.once&&o.called)return!1;if(o.repeat||(m=o),!o.input)for(c=e.target;c!=document.body;){if(!o.input&&("input"===c.tagName.toLowerCase()||"textarea"===c.tagName.toLowerCase()))return!1;if(!o.input&&c.hasAttribute("contenteditable")&&c.getAttribute("contenteditable")===!0)return!1;c=c.parentNode}if(null!==o.ignoreClass){var s=o.ignoreClass.replace(/\s+/g,"|");if(s=new RegExp(s,"gi"),s.test(this.className))return!1}o.waiting=!0,o.exec.call(e,n.bind(o)),o.executed=!0,o.called=!0,p=[],a=""}}),e(t,"keyup",function(e){var t,n,r="which"in e?e.which:e.keyCode;return n=p.join("+").toLowerCase(),17===r||18===r?(e.preventDefault(),g=!1,p=[],!1):(t=p.indexOf(keyCodes.numerical[r]),t>-1&&p.splice(t,1),null!==m&&(m.executed=!1),void 0)})}function a(e){return e=e.toLowerCase(),y.cmd.hasOwnProperty(e)?y.cmd[e]:null}function o(e){return e=e.toLowerCase(),y.cmd.hasOwnProperty(e)?(delete y.cmd[e],!0):!1}function c(e,t){var n={};if("string"!=typeof e)return new Error("The first argument for register must be a string combinator.");if(e=e.toLowerCase(),/Mac|iPod|iPhone|iPad/.test(navigator.platform)&&e.replace("ctrl","meta"),w.indexOf(e)>-1==!0)return new Error(e+" cannot be used, this is a system specific combinator");if(y.cmd.hasOwnProperty(e))return new Error(e+" already exists. Please release this combinator before overwriting.");if(e.split("+").length>4)return new Error(e+" is too long, the system can only handle four consecutive keys pressed.");for(var r in f)t.hasOwnProperty(r)&&"executed"!==r&&"called"!==r&&"waiting"!==r?n[r]=t[r]:n[r]=f[r];y.cmd[e]=n}function s(e){if(e=e.toLowerCase(),y.cmd.hasOwnProperty(e)){var t=y.cmd[e];t.waiting=!0,t.exec.call(t,n.bind(t))}}function d(n,r){return r||(r=document),h=!0,e(r,"keydown",function i(e){if(h){var r="which"in e?e.which:e.keyCode,a=keyCodes.numerical[r];n.call(this,a,e)}else t(this,"keydown",i)}),{stop:l}}function l(){h=!1}function u(e){return e=e.toLowerCase(),y.cmd.hasOwnProperty(e)?(y.cmd[e].executed=!1,y.cmd[e].called=!1,!0):!1}var w=["ctrl+n","ctrl+shift+n","ctrl+t","ctrl+shift+t","ctrl+w","ctrl+shift+w"],f={title:null,exec:function(){},repeat:!0,input:!0,once:!1,executed:!1,called:!1,ignoreClass:null},h=!1,p=[],m=null,g=!1;window.keyCodes={},window.keyCodes.numerical={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",19:"pause/break",20:"caps lock",27:"escapse",33:"page up",34:"page down",35:"end",36:"home",37:"left arrow",38:"up arrow",39:"right arrow",40:"down arrow",45:"insert",46:"delete",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",91:"left window",106:"multiply",107:"add",109:"subtract",110:"decimal point",111:"divide",186:"semi-colon",187:"equal sign",188:"comma",189:"dash",190:"period",191:"forward slash",192:"grave accent",219:"open bracket",220:"back slash",221:"close bracket",222:"single quote"},window.keyCodes.literal={backspace:8,tab:9,enter:13,shift:16,ctrl:17,alt:18,"pause/break":19,"caps lock":20,escape:27,"page up":33,"page down":34,end:35,home:36,"left arrow":37,"up arrow":38,"right arrow":39,"down arrow":40,insert:45,"delete":46,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,"left window":91,multiply:106,add:107,subtract:109,"decimal point":110,divide:111,"semi-colon":186,"equal sign":187,comma:188,dash:189,period:190,"forward slash":191,"grave accent":192,"open bracket":219,"back slash":220,"close bracket":221,"single quote":222};var y={fetch:a,register:c,listen:i,release:o,reset:u,trigger:s,record:d,cmd:{}};window.combinator=y}();
Mark it all pages, or you can add it directly to the head of your document by editing the head template.
So basically you're done, but how do we listen to shortcuts or anything like that? Well there are eight properties to the combinator object, and below is the explanation to each.
[ic]combinator.register(shortcut,properties);[/ic]
The register property is a function to add new shortcuts, I prefer you to use this method but you can write out the list of them yourself using the [ic]combinator.cmd[/ic] property. So let's take a look at how to use the register property.
- Code:
[panda=js]
combinator.register("ctrl+b",{
title: null,
exec:function() {
console.log("hello");
},
input: true,
repeat:true,
once:false,
ignoreClass: null
});
So the above is a simple register example. Some notes to understand, most of what you see is the default value except for the exec property. So if you want your command to work inside inputs (inputs, textareas, contenteditable elements) then you don't need to add it. Basically if the default is true for you, you don't need to add it at all. So let's go over each of these properties separately.
[ic] title [/ic] is the name of the command
[ic] exec [/ic] is the function to execute when the command is entered. It has one argument which is a sequence to listen for after. See example below to understand. The [ic]this[/ic] inside the scope of exec is the [ic]event[/ic] property of keydown.
[ic] input [/ic] default is true, meaning the commands will work inside any input element, textarea, or contenteditable element.
[ic] repeat [/ic] default is true, repeat means if we hold down the command it will repeat itself, if we want it to execute once instead of repeat set this to false. Though remember the user just has to lift their finger from the key and press it again and it'll work.
[ic] once [/ic] default is false, once is basically what it says. The command will only fire once no matter how many times the user presses it the command. You can reset this later if you want.
[ic] ignoreClass [/ic] default is null, if you want an element such as a textarea that you don't want the commands to work in then create a class such as "ignore-halt" and add it to the textarea, this is good because if you only want one textarea to not work and other inputs to work then this is how it is done.
[ic]combinator.listen(location);[/ic]
listen makes the location we set, default argument is document, listen for the commands. Later this will be useful when combinator allows for multiple instances of combinator. If you want the entire document to listen then just leave the argument alone.
[ic]combinator.fetch(shortcut);[/ic]
fetch gets the object we want from our [ic]combinator.cmd[/ic] object, this is just useful so we don't have to write [ic]combinator.cmd["ctrl+b"][/ic] especially since I made cases all lower case and if you think that your command is actually [ic]ctrl+B[/ic] it will actually be [ic]ctrl+b[/ic]
[ic]combinator.release(shortcut);[/ic]
Release, this is a special function since we don't allow you to overwrite combinators you must first release the original from the object. This is incase you forget that you already defined the combinator.
Returns true if the object is released.
[ic]combinator.trigger(shortcut);[/ic]
trigger makes it so you can trigger the [ic]exec[/ic] function that you defined without having to press the commands.
[ic]combinator.record(callback,location);[/ic]
record the event on keydown happening in a specific location, default location is document again. This is great to use if you want to just listen to what keys were pressed. Example later on. Save this in a variable because it returns a function to stop listening.
[ic]combinator.cmd[/ic]
The cmd object is all the commands you've entered in.
Examples:
- Code:
[panda=js]
combinator.register("ctrl+b",{
title:"my new ctrl+b",
exec:function(sequence) {
sequence("a",function(e){
console.log("A was pressed");
},document);
}
});
This combinator will work as follows. So first we press [ic]ctrl+b[/ic] now we wait and if we press [ic]a[/ic] the message that is logged will be "A was pressed".
Remember that if a new combinator is started that the old sequence that was waiting will be destroyed.
- Code:
[panda=js]
var recorder = combinator.record(function(literal, e){
console.log(literal + " is " + e.which + " in a numerical key");
recorder.stop();
},document.body);
What this does is if we press "enter" it will log "Enter is 13 in a numerical key". Then the recorder will stop.
A complex example:
- Code:
[panda=js]
combinator.register("ctrl+b",{
title:"my new ctrl+b",
exec:function(sequence) {
sequence("a",function(e){
combinator.reset("ctrl+b");
});
},
once:true
});
This will actually be a combinator that runs only once, until we press "a" then it will reset the combinator and we can start over again.
If you'd like any more examples please give me an idea and I'll write an example for you.