{"id":3399,"date":"2015-01-12T16:31:21","date_gmt":"2015-01-12T22:31:21","guid":{"rendered":"http:\/\/bililite.com\/blog\/?p=3399"},"modified":"2015-01-12T16:31:21","modified_gmt":"2015-01-12T22:31:21","slug":"rethinking-keymap","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2015\/01\/12\/rethinking-keymap\/","title":{"rendered":"Rethinking <code>$.keymap<\/code>"},"content":{"rendered":"<p><a href=\"\/inc\/jquery.keymap.js\">Download the code<\/a>.<\/p>\n<p><a href=\"\/blog\/blogfiles\/keymap.html\">See the demo<\/a>.<\/p>\n<p><a href=\"\/blog\/blogfiles\/vi\/hotkeys.html\">See the hotkeys demo<\/a>.<\/p>\n<p>Dealing with <code>keydown<\/code> events is a pain, since the event object has always encoded the key as an arbitrary numeric code, so any program that deals with key-related events has something like:<\/p>\n<pre><code class=\"language-javascript\" >var charcodes = {\r\n\t 32\t: ' ',\r\n\t  8\t: 'Backspace',\r\n\t 20\t: 'CapsLock',\r\n\t 46\t: 'Delete',\r\n\t 13\t: 'Enter',\r\n\t 27\t: 'Escape',\r\n\t 45\t: 'Insert',\r\n\t144\t: 'NumLock',\r\n\t  9\t: 'Tab'\r\n};<\/code><\/pre>\n<p>And so on. There is <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-3-Events\/#key\">a proposal<\/a> to add a <code>key<\/code> field that uses<a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-3-Events-key\/\"> standardized names for the keys<\/a>, but only Firefox and IE <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/KeyboardEvent.key\">implement that right now<\/a>. The time is ripe for a polyfill to bring Chrome and Safari into the twenty-first century, and I'd already worked on something similar with my <a href=\"http:\/\/bililite.com\/blog\/2013\/02\/22\/parsing-keydown-events\/\"><code class=\"language-javascript\" >$.keymap<\/code> plugin<\/a>.<\/p>\n<p>So I updated that plugin to simply implement the <code>key<\/code> field in <code>keydown<\/code> and <code>keyup<\/code> events. Now just include the plugin and you can do things like:<\/p>\n<pre><code class=\"language-javascript\" >$('textarea').keydown(function(event){\r\n  if (event.key == 'Escape') { do something useful }\r\n});<\/code><\/pre>\n<p><!--more--><\/p>\n<p>I also added a nonstandard field called <code>keymap<\/code> that includes the state of the modifier keys, using <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.forms.sendkeys.aspx\">Microsoft's sendkeys<\/a> notation, with <code>+<\/code> for shift, <code>^<\/code> for control and <code>%<\/code> for alt. While Windows doesn't have a meta key, it's part of the standard key event, and I included that with <code>~<\/code> for meta. I can't test it, though, so feedback on whether it works would be nice. So now rather than doing:<\/p>\n<pre><code class=\"language-javascript\" >$('textarea').keydown(function(event){\r\n  if (event.key == 'Enter' && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) { do something useful }\r\n});<\/code><\/pre>\n<p>you can do:<\/p>\n<pre><code class=\"language-javascript\" >$('textarea').keydown(function(event){\r\n  if (event.keymap == '^+Enter') { do something useful }\r\n});<\/code><\/pre>\n<p>There's one exception: the standard uses <code>' '<\/code> for the <code>key<\/code> value for the space bar, where IE uses <code>Spacebar<\/code>. I think the latter is more useful, since I can't see the space character (and I want to use space as a delimiter). So the <code>key<\/code> field is <code>' '<\/code>, per the standard, but <code>keymap<\/code> use <code>Spacebar<\/code>.<\/p>\n<p>It will normalize synthetic keystrokes too, so doing:<\/p>\n<pre><code class=\"language-javascript\" >$(el).trigger({type: 'keydown', keymap: '^%a'});<\/code><\/pre>\n<p>or<\/p>\n<pre><code class=\"language-javascript\" >$(el).trigger({type: 'keydown', key: 'a', ctrlKey: true, altKey: true});<\/code><\/pre>\n<p>allows you to do<\/p>\n<pre><code class=\"language-javascript\" >$(el).on('keydown', function (event){\r\n  if (event.key == 'a' && event.ctrlKey && event.altKey) {do something useful}\r\n});<\/code><\/pre>\n<p>or simply<\/p>\n<pre><code class=\"language-javascript\" >$(el).on('keydown', function (event){\r\n  if (event.keymap == '^%a') {do something useful}\r\n});<\/code><\/pre>\n<p>The <code>keymap<\/code> passed as part of a synthetic event will be normalized to the standard key names, as will the <code>key<\/code> field. Indicate shifted letters with uppercase; thus control-a is <code>'^a'<\/code> and control-shift-a is <code>'^A'<\/code>, not <code>'+^a'<\/code>. Redundant shifts are removed: <code>'+@'<\/code> is normalized to <code>'@'<\/code>. It also allows for <a href=\"http:\/\/polarhome.com\/vim\/manual\/v71\/intro.html#key-notation\">VIM<\/a> and <a href=\"https:\/\/github.com\/jeresig\/jquery.hotkeys\">jquery.hotkeys<\/a> notation; thus both <code >'&lt;C-a&gt;<\/code> and <code>'ctrl+a'<\/code> are normalized to <code>'^a'<\/code>. Please look at the source for <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/jquery.keymap.js\"><code>aliasgenerator<\/code><\/a> for all the translations.<\/p>\n<h3>Hotkeys<\/h3>\n<p>Writing <code class=\"language-javascript\" >if (event.keymap = '^%a') { ...<\/code> gets old, and I wanted to be able to look for sequences of characters as well (<code>Escape<\/code> <code>%<\/code> if I'm trying to implement emacs). John Resig wrote a <a href=\"https:\/\/github.com\/jeresig\/jquery.hotkeys\">hotkeys plugin<\/a> and I added <code>keyup<\/code> and <code>keydown<\/code> handlers to implement the same idea. Add a <code>keys<\/code> field to the <code>data<\/code> argument and the handler will only be called if the <code>keymap<\/code> of that event matches it (it should be called <code>keymaps<\/code> rather than <code>keys<\/code>, for consistency. I'm keeping it this way). Use a space-delimited list of keys to match a sequence (and use <code>Spacebar<\/code> to match the space key). The <code>keys<\/code> field will be normalized as above, so any notation works. The Event object will have an additional field, <code>hotkeys<\/code>, which is the sequence of keys matched.<br \/>\nThus:<\/p>\n<pre><code class=\"language-javascript\" >$(el).on('keydown', {keys: '^%a'}, handler);\r\n$(el).off('keydown', {keys: '^%a'}); \/\/ works\r\n$(el).off('keydown', handler); \/\/ DOES NOT WORK. The plugin changes the handler internally so the remove mechanism can't search for it.\r\n\r\n$('body').on('keydown', {\r\n    keys: 'up up down down left right left right b a' \/\/ This will be normalized to ArrowUp ArrowUp ArrowDown ArrowDown ArrowLeft ArrowRight ArrowLeft ArrowRight b a\r\n  }, \r\n  function() {alert('Konami!')}\r\n);\r\n\r\n$('body').on('keydown', 'input', {keys: '^A'}, function() {alert('Control A was pressed in an input')} ); \/\/ selectors work\r\n\r\n$(el).keydown ({keys: 'C-x C-s'}, function(event){ \/\/ $.keydown is just a shortcut for $.on('keydown'...\r\n  \/\/ do emacs save-buffer, somehow...\r\n\r\n  console.log (event.hotkeys); \/\/ displays '^x ^s', the \"normalized\" form of the keystrokes\r\n});<\/code><\/pre>\n<p>You can also use a regular expression to match keys, but that has to match the final normalized keystrokes, including exactly one space between keys:<\/p>\n<pre><code class=\"language-javascript\" >$(el).keydown({keys: \/\" [a-z] p\/}, function (event){\r\n  \/\/ the vi put command\r\n  var buffer = event.hotkeys.charAt(1); \/\/ the letter matching [a-z] above\r\n  \/\/ do the put command\r\n});<\/code><\/pre>\n<p>There's one other option: <code>allowDefault<\/code>. Normally if a sequence is in the process of being matched, the intermediate keystrokes are discarded. Set <code>allowDefault<\/code> to <code class=\"language-javascript\" >true<\/code> to pass them through. So, for the Konami code you would still want the arrow keys to work:<\/p>\n<pre><code class=\"language-javascript\" >$('body').on('keydown', {\r\n    keys: '{up} {up} {down} {down} {left} {right} {left} {right} b a', \/\/ braces notation from Microsoft's sendkeys will be normalized\r\n    allowDefault: true \/\/ the arrow keys will still work\r\n  }, \r\n  function() {alert('Konami!')}\r\n);<\/code><\/pre>\n<h4>Events<\/h4>\n<p>The hotkeys plugin also triggers two other events: <code class=\"language-javascript\" >'keymapprefix'<\/code> when the event is the prefix of a valid sequence, and <code class=\"language-javascript\" >'keymapcomplete'<\/code> when a sequence is matched. Both are sent with an additional parameter indicating what keys were pressed so far (in the normalized notation). So:<\/p>\n<pre><code class=\"language-javascript\" >$('body').on('keymapprefix keymapcomplete', function(event, keys){\r\n\talert ('sequence pressed: '+keys);\r\n})<\/code><\/pre>\n<h3>Localization<\/h3>\n<p>The code as it stands assumes a standard US-English keyboard. <a href=\"http:\/\/stackoverflow.com\/questions\/8892238\/detect-keyboard-layout-with-javascript\">There's no way for javascript code to know what keyboard the user has<\/a>. This is bad in browsers that do not support <code>key<\/code> directly, since all I get is the <code>keyCode<\/code>, and that corresponds to a different character in every keyboard. But even in browsers that support <code>key<\/code> natively, I still need to know whether the key should be marked with a <code>+<\/code> when shifted; for instance, '@' with <code>shiftKey<\/code> set should have a <code>keymap<\/code> of <code>@<\/code> (no <code>+<\/code>, since that character implies the shift), but the 'DEL' key with <code>shiftKey<\/code> set should have a <code>keymap<\/code> of <code>+Delete<\/code>. This is even more of a problem with the AltGr key, which in most keyboards does not change the printed character and thus needs to be indicated in <code>keymap<\/code>.<\/p>\n<p>As far as I can tell, there are two modifier keys that affect the printed character: shift and AltGr. <a href=\"http:\/\/stackoverflow.com\/questions\/17857404\/why-does-alt-gr-have-the-same-keycode-as-ctrl\">In all modern browsers<\/a>, AltGr is indicated by setting both <code>ctrlKey<\/code> and <code>altKey<\/code>, so <code>keymap<\/code> indicates it with <code>^%<\/code>; so AltGr and the A key produces <code>^%a<\/code>.<\/p>\n<p>You can change the assumed keyboard layout with <code class=\"language-javascript\" >$.keymap.setlayout(layout)<\/code>, where <code class=\"language-javascript\" >layout<\/code> is a object of the form:<\/p>\n<pre><code class=\"language-javascript\" >{\r\n  normal: \"`1234567890-=\\\\qwertyuiop[]asdfghjkl;'zxcvbnm,.\/\",\r\n  shift: ... \/\/ keyboard layout for shifted keys\r\n  alt: ... \/\/ keyboard layout for the AltGr key\r\n  shift_alt: ... \/\/ keyboard layout for Shift and AltGr key both held down\r\n}<\/code><\/pre>\n<p>Each string should have 47 characters, just the rows of the keyboard left to right and top to bottom (it assumes the keyboard that has an extra-large Enter key and the backslash to the right of '=', not right of ']'). The format is that used by <a href=\"http:\/\/allanguages.info\/\">Virtual Keyboard<\/a>, which I <a href=\"\/blog\/2013\/01\/30\/jsvk-a-jquery-plugin-for-virtualkeyboard\/\" title=\"jsvk, a jQuery Plugin for VirtualKeyboard\">use extensively<\/a>. Unfortunately, the <a href=\"http:\/\/debugger.ru\">development site<\/a> seems to be dead, but you can download the <a href=\"http:\/\/sourceforge.net\/projects\/jsvk\/\">source from SourceForge<\/a> and look in the layouts folder for layout formats for lots of different languages.<\/p>\n<p>Keys that do not change with the modifier should be indicated with <code class=\"language-javascript\" >'\\00'<\/code>, or use an abbreviated format: an object <code class=\"language-javascript\" >{index1: substring1, index2: substring2 ...}<\/code> where <code class=\"language-javascript\" >index<\/code> is the starting index of the given substring. For all keys that have an uppercase, if the <code>shift<\/code> string is not set, it will be set to <code class=\"language-javascript\" >c.toUpperCase()<\/code>. Any of the strings may be omitted, and will be assumed to be all blank. For example:<\/p>\n<pre><code class=\"language-javascript\" >var layouts = {\r\n\tUS :{\r\n\t\tnormal:'`1234567890-=\\\\qwertyuiop[]asdfghjkl;\\'zxcvbnm,.\/',\r\n\t\tshift:{0:'~!@#$%^&*()_+|',24:'{}',35:':\"',44:'<>?'} \/\/ note the uppercase letters are skipped; the plugin can figure that out for itself\r\n\t},\r\n\tUK :{\r\n\t\tnormal:'`1234567890-=#qwertyuiop[]asdfghjkl;\\'zxcvbnm,.\/',\r\n\t\tshift:{0:'\u00ac!\"\u00a3$%^&*()_+~',24:'{}',35:':@',44:'<>?'},\r\n\t\talt:{0:'\u00a6',4:'\u20ac',16:'\u00e9',20:'\u00fa\u00ed\u00f3',26:'\u00e1'} \/\/ altGr keys\r\n\t},\r\n\tIsrael : {\r\n\t\tnormal:';1234567890-=\\\\\/\\'\u05e7\u05e8\u05d0\u05d8\u05d5\u05df\u05dd\u05e4][\u05e9\u05d3\u05d2\u05db\u05e2\u05d9\u05d7\u05dc\u05da\u05e3,\u05d6\u05e1\u05d1\u05d4\u05e0\u05de\u05e6\u05ea\u05e5.',\r\n\t\tshift:{0:'~!@#$%^&*)(_+|QWERTYUIOP}{ASDFGHJKL:\"ZXCVBNM><'+'?'}, \/\/ I had to split the < and ? up to avoid confusing my syntax highlighter\r\n\t\talt:{3:'\u20ac\u20aa\u00b0\u05ab\u05bd\u00d7\u200e\u200f',11:'\u200f\u05be\u2013\u05bb\u05c2',16:'\u05b8\u05b3',19:'\u05f0\u05b9',23:'\u05b7\u05b2\u05bf\u05b0\u05bc',30:'\u05f1\u05f2\u05b4',34:'\u201d\u201e\u05f4',43:'\u05b5\u2019\u201a\u00f7'}\r\n\t}\r\n};\r\n$.keymap.setlayout(layouts.Israel);<\/code><\/pre>\n<h3>Utilities<\/h3>\n<p><code class=\"language-javascript\" >$.keymap(event)<\/code> is the basic plugin; it normalizes the Event object as described (setting <code>key<\/code> and <code>keymap<\/code>) and returns the value of <code>keymap<\/code>. Possibly useful if you are handling events straight with <code class=\"language-javascript\" >addEventListener()<\/code>; the plugin patches the jQuery handing to automatically do this.<\/p>\n<p><code class=\"language-javascript\" >$.keymap.normalize(event)<\/code> is the same as <code class=\"language-javascript\" >$.keymap(event)<\/code> but returns <code class=\"language-javascript\" >event<\/code>.<\/p>\n<p><code class=\"language-javascript\" >$.keymap.normalizeString(string)<\/code> is the workhorse function to normalize the key descriptions; <code class=\"language-javascript\" >$.keymap.normalizeString('ctrl-esc')<\/code> returns <code class=\"language-javascript\" >'^Escape'<\/code>.<\/p>\n<p><code class=\"language-javascript\" >$.keymap.normalizeList(string)<\/code> does the same for a space-delimited list of key descriptions; <code class=\"language-javascript\" >$.keymap.normalizeList('ctrl-esc %   alt-5')<\/code> returns <code class=\"language-javascript\" >'^Escape % %5'<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Download the code. See the demo. See the hotkeys demo. Dealing with keydown events is a pain, since the event object has always encoded the key as an arbitrary numeric code, so any program that deals with key-related events has something like: var charcodes = { 32 : ' ', 8 : 'Backspace', 20 : [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,5],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3399"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=3399"}],"version-history":[{"count":27,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3399\/revisions"}],"predecessor-version":[{"id":3426,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3399\/revisions\/3426"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=3399"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=3399"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=3399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}