{"id":3429,"date":"2015-01-14T17:39:55","date_gmt":"2015-01-14T23:39:55","guid":{"rendered":"http:\/\/bililite.com\/blog\/?p=3429"},"modified":"2015-01-14T17:42:53","modified_gmt":"2015-01-14T23:42:53","slug":"rethinking-fn-sendkeys","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2015\/01\/14\/rethinking-fn-sendkeys\/","title":{"rendered":"Rethinking <code>$.fn.sendkeys<\/code>"},"content":{"rendered":"<p>See the <a href=\"#demo\">demo<\/a>.<br \/>\nSee the <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/jquery.sendkeys.js\">source code<\/a>, which depends on <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/bililiteRange.js\">bililiteRange<\/a>.<\/p>\n<p>Modern browsers won't let synthetic events (triggered with <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/EventTarget.dispatchEvent\">dispatchEvent<\/a>) execute their default actions (meaning the action that would occur if the event was triggered by a user action). The Event object has a read-only field called <code>isTrusted<\/code> that is <code>false<\/code> for anything but unmodified user-initiated events. These are called <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-3-Events\/#trusted-events\">\"trusted events\"<\/a>, and I understand the justification, but they go too far. It makes it impossible to implement a virtual keyboard, since triggering keydown or keypress events aren't trusted and won't insert the character (the default action). <\/p>\n<p>Fortunately, <a href=\"http:\/\/bililite.com\/blog\/2011\/01\/17\/cross-browser-text-ranges-and-selections\/\" title=\"Cross-Browser Text Ranges and Selections\">bililiteRange<\/a> and especially <a href=\"http:\/\/bililite.com\/blog\/2015\/01\/14\/bililiterange-sendkeys\/\" title=\"bililiteRange.sendkeys\">bililiteRange.sendkeys<\/a> can insert characters and do other manipulations on the page. So I created a jQuery plugin that uses bililiteRange.sendkeys to catch keydown events and implement them as well as possible.<br \/>\nJust include the source code and keydown events get a new default handler (so it can be cancelled by preventDefault) that looks at the <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-3-Events-key\/\"><code>key<\/code> field<\/a>. If it is a single character, that character is inserted at the selection. If it is more than one character long, it is assumed to be a <code>sendkeys<\/code> command like <code>ArrowLeft<\/code> and is sent as <code class=\"language-javascript\" >sendkeys('{'+key+'}')<\/code>.<br \/>\nI used the modern <code class=\"language-javascript\" >Event.key<\/code> rather than <code class=\"language-javascript\" >Event.which<\/code>, so I don't have to translate keyCodes. If you need to use the old way, see my <a href=\"http:\/\/bililite.com\/blog\/2015\/01\/12\/rethinking-keymap\/\" title=\"Rethinking $.keymap\">keymap<\/a> plugin. <\/p>\n<p>Thus now, <code class=\"language-javascript\" >$('textarea').trigger({type: 'keydown', key: 'A'})<\/code> will work as expected, as will <code class=\"language-javascript\" >$('textarea').trigger({type: 'keydown', key: 'Backspace'})<\/code>.<\/p>\n<h3>The actual plugin<\/h3>\n<p>Under the hood, this uses a very simple jQuery plugin that just calls <code class=\"language-javascript\" >bililiteRange.sendkeys()<\/code>. It also turns <code class=\"language-javascript\" >'\\n'<\/code> in the string into <code class=\"language-javascript\" >'{Enter}'<\/code>, which I thought would be useful but has actually not turned out that way. Putting the <code class=\"language-javascript\" >'\\n'<\/code> in braces (<code class=\"language-javascript\" >'{\\n}'<\/code> prevents the replacement.<br \/>\nThe plugin itself is:<\/p>\n<pre><code class=\"language-javascript\" >$.fn.sendkeys = function (x){\r\n  x = x.replace(\/([^{])\\n\/g, '$1{enter}'); \/\/ turn line feeds into explicit break insertions, but not if escaped\r\n  return this.each( function(){\r\n    bililiteRange(this).bounds('selection').sendkeys(x).select();\r\n    this.focus();\r\n  });\r\n};<\/code><\/pre>\n<p><script src=\"\/inc\/bililiteRange.js\" type=\"text\/javascript\"><\/script><br \/>\n<script src=\"\/inc\/jquery.sendkeys.js\" type=\"text\/javascript\"><\/script><br \/>\n<script type=\"text\/javascript\">\n$(function(){\n\t$('.output, .test, .wrap').css({\n\t\tfontSize: '100%',\n\t\twidth: '15em',\n\t\tpadding: '0.5em',\n\t\toverflow: 'auto',\n\t\tborder: '1px solid blue'\n\t});\n\t$('.phonepad ').css({\n\t\twidth: '20em',\n\t\tborder: '1px solid gray',\n\t\tpadding: '0px',\n\t\tfloat: 'left'\n\t});\n\t$('.phonepad input').css({\n\t\twidth: '3em',\n\t\theight: '2em',\n\t\tmargin: '.5em'\n\t});\n\t$('div.test').css({\n\t\tborder: '1px solid gray'\n\t});\n});\n<\/script><\/p>\n<h3>Demo<\/h3>\n<pre><code class=\"language-javascript demo\">\r\n$('.selectoutput').click(function(){\r\n\t$('.output').removeClass('selected');\r\n\tvar index = $(this).parents('th').index();\r\n\t$('.output').eq(index).addClass('selected').focus();\r\n});\r\n$('div.test input:button').click(function(){\r\n\t$('.output.selected').sendkeys($('div.test input:text').val());\r\n});\r\n$('div.wrap input:button').click(function(){\r\n\tvar tag = $('div.wrap select').val();\r\n\t$('.output.selected').sendkeys('&lt;'+tag+'&gt;{selection}{mark}&lt;\/'+tag+'&gt;');\r\n});\r\n$('.phonepad input').click(function(){\r\n\t$('.output.selected').trigger({type: 'keydown', key: this.name || this.value});\r\n});\r\n$('.output').each(function(){\r\n\tbililiteRange(this); \/\/ initialize the selection tracking\r\n}).on('keydown', function(evt){\r\n\tif ($('#overridepad').is(':checked')){\r\n\t\talert (evt.key);\r\n\t\tevt.preventDefault();\r\n\t}\r\n}).on('keypress', function(evt){\r\n\t$('#keypress').text($('#keypress').text()+' '+evt.which);\r\n}).on('sendkeys', function(evt){\r\n\t$('#sendkeys').text($('#sendkeys').text()+' '+evt.which);\r\n}).on('focus', function(){\r\n\tvar index = $(this).parents('td').index();\r\n\t$('.output').removeClass('selected');\r\n\t$('.output').eq(index).addClass('selected')\r\n\t$('.selectoutput').eq(index).attr('checked',true);;\r\n});\r\n<\/code><\/pre>\n<pre><code class=\"language-html demo\">\r\n&lt;div&gt;\r\n\t&lt;table style=&quot;width: 100%&quot; border=&quot;2&quot; id=\"demo\" &gt;\r\n\t\t&lt;thead&gt;\r\n\t\t\t&lt;tr&gt;\r\n\t\t\t\t&lt;th&gt;&lt;label&gt;\r\n\t\t\t\t\t&lt;input type=&quot;radio&quot; class=\"selectoutput\" name=&quot;selectoutput&quot; checked=\"checked\" \/&gt;\r\n\t\t\t\t\t&lt;code&gt;&amp;lt;input&amp;gt;&lt;\/code&gt;\r\n\t\t\t\t&lt;\/label&gt;&lt;\/th&gt;\r\n\t\t\t\t&lt;th&gt;&lt;label&gt;\r\n\t\t\t\t\t&lt;input type=&quot;radio&quot; class=\"selectoutput\" name=&quot;selectoutput&quot; \/&gt;\r\n\t\t\t\t\t&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;\/code&gt;\r\n\t\t\t\t&lt;\/label&gt;&lt;\/th&gt;\r\n\t\t\t\t&lt;th&gt;&lt;label&gt;\r\n\t\t\t\t\t&lt;input type=&quot;radio&quot; class=\"selectoutput\" name=&quot;selectoutput&quot; \/&gt;\r\n\t\t\t\t\t&lt;code&gt;&amp;lt;div contenteditable&amp;gt;&lt;\/code&gt;\r\n\t\t\t\t&lt;\/label&gt;&lt;\/th&gt;\r\n\t\t\t\t&lt;th&gt;&lt;label&gt;\r\n\t\t\t\t\t&lt;input type=&quot;radio&quot; class=\"selectoutput\" name=&quot;selectoutput&quot; \/&gt;\r\n\t\t\t\t\t&lt;code&gt;&amp;lt;div&amp;gt;&lt;\/code&gt;\r\n\t\t\t\t&lt;\/label&gt;&lt;\/th&gt;\r\n\t\t\t&lt;\/tr&gt;\r\n\t\t&lt;\/thead&gt;\r\n\t\t&lt;tbody&gt;\r\n\t\t\t&lt;tr&gt;\r\n\t\t\t\t&lt;td&gt;&lt;input type=&quot;text&quot; class=&quot;output selected&quot; \/&gt;&lt;\/td&gt;\r\n\t\t\t\t&lt;td&gt;&lt;textarea class=&quot;output&quot;&gt;&lt;\/textarea&gt;&lt;\/td&gt;\r\n\t\t\t\t&lt;td&gt;&lt;div class=&quot;output&quot; contentEditable=\"true\"&gt;&lt;\/div&gt;&lt;\/td&gt;\r\n\t\t\t\t&lt;td&gt;&lt;div class=&quot;output&quot; &gt;This is not editable text&lt;\/div&gt;&lt;\/td&gt;\r\n\t\t\t&lt;\/tr&gt;\r\n\t\t&lt;\/tbody&gt;\r\n\t&lt;\/table&gt;\r\n&lt;div class=&quot;phonepad&quot;&gt;\r\n&lt;input type=&quot;button&quot; name=&quot;ArrowLeft&quot; value=&quot;&amp;larr;&quot;\/&gt;&lt;input type=&quot;button&quot; name=&quot;ArrowRight&quot; value=&quot;&amp;rarr;&quot;\/&gt;&lt;input type=&quot;button&quot; name=&quot;Backspace&quot; value=&quot;BS&quot;\/&gt;&lt;input type=&quot;button&quot; name=&quot;selectall&quot; value=&quot;All&quot;\/&gt;&lt;br\/&gt;\r\n&lt;input type=&quot;button&quot; value=&quot;7&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;8&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;9&quot; \/&gt;&lt;br\/&gt;\r\n&lt;input type=&quot;button&quot; value=&quot;4&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;5&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;6&quot; \/&gt;&lt;br\/&gt;\r\n&lt;input type=&quot;button&quot; value=&quot;1&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;2&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;3&quot; \/&gt;&lt;br\/&gt;\r\n&lt;input type=&quot;button&quot; value=&quot;*&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;0&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;#&quot; \/&gt;&lt;input type=&quot;button&quot; name=&quot;Enter&quot; value=&quot;&amp;crarr;&quot;\/&gt;\r\n&lt;\/div&gt;\r\n&lt;label&gt;Alert on keydown event: &lt;input type=checkbox id=overridepad \/&gt;&lt;\/label&gt;\r\n&lt;div class=&quot;test&quot;&gt;&lt;input type=&quot;text&quot; \/&gt;&lt;input type=&quot;button&quot; value=&quot;test&quot;\/&gt;&lt;\/div&gt;\r\n&lt;div class=&quot;wrap&quot;&gt;&lt;select&gt;&lt;option&gt;em&lt;\/option&gt;&lt;option&gt;strong&lt;\/option&gt;&lt;option&gt;del&lt;\/option&gt;&lt;\/select&gt;&lt;input type=&quot;button&quot; value=&quot;Wrap Selection&quot;\/&gt;&lt;\/div&gt;\r\n\r\n&lt;div id=\"keypress\"&gt;keypress event.which:&lt;\/div&gt;\r\n&lt;div id=\"sendkeys\"&gt;sendkeys event.which:&lt;\/div&gt;\r\n&lt;\/div&gt;\r\n&lt;div style=\"clear:both\" \/&gt;\r\n<\/code><span id=\"demo\"><\/span><\/pre>\n<p>The phone pad keys use <code class=\"language-javascript\" >$().trigger({type: 'keydown', key: key})<\/code>. The test button does <code class=\"language-javascript\" >$().sendkeys(textbox.value)<\/code>. The wrap button does <code class=\"language-javascript\" >$().sendkeys('&lt;tag&gt;{selection}{mark}&lt;\/tag&gt;')<\/code>. Note that the <code class=\"language-javascript\" >trigger<\/code> code does not affect the non-editable DIV, while <code class=\"language-javascript\" >sendkeys<\/code> does.<br \/>\nThe \"Alert on keydown event\" checkbox attaches a handler to the <code>keydown<\/code> event which calls <code>event.preventDefault<\/code>, showing that the text entry and keypress events do not occur.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>See the demo. See the source code, which depends on bililiteRange. Modern browsers won't let synthetic events (triggered with dispatchEvent) execute their default actions (meaning the action that would occur if the event was triggered by a user action). The Event object has a read-only field called isTrusted that is false for anything but unmodified [&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\/3429"}],"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=3429"}],"version-history":[{"count":16,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3429\/revisions"}],"predecessor-version":[{"id":3460,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3429\/revisions\/3460"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=3429"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=3429"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=3429"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}