The $.fn.sendkeys Plugin
This is an updated version of my original $.fn.sendkeys post.
Last updated 2012-09-06.
I wanted to make a general-purpose onscreen keypad, and wasted a huge amount of time trying to find a way to
simulate a keypress.
$(element).trigger("keypress",...) won't work. Neither will keyup or keydown.
For security reasons, I guess, you can't tell an element to pretend a key was pressed. The browser is too
worried that you will access menus or something.
So I wrote my own plugin and named it after Microsoft's sendkeys
which does similar things. For any editable element elem,
$(elem).sendkeys(string) inserts string at the insertion point or selection.
It's the insertion point sensitivity
that makes it more sophisticated than elem.value += string.
It depends on my bililiteRange routines to manage the selections in a cross-browser way.
Downloads
Download $.fn.sendkeys.
Download bililiteRange.
See the demo.
Usage
It's very simple, $(element).sendkeys(string) inserts string at the insertion point in an input, textarea or other element with contenteditable=true. If the insertion point is not currently in the element, it remembers where the insertion point was when sendkeys was last called (if the insertion point was never in the element, it appends to the end).
Special keys
"Special keys" are indicated by curly braces. The following are defined:
{backspace}- Delete backwards
{del}- Delete forwards
{rightarrow}- Move the insertion point to the right
{leftarrow}- Move the insertion point to the left
{selectall}- Select the entire field
{enter}- Insert a newline.
'\n'does the same thing. Warning: Incontenteditableelements,{enter}is flaky and inconsistent across browsers. This is due to the flakiness ofcontenteditableitself; I can't figure out what to do about this. {{}- Inserts a
{by itself {selection}- Inserts the text of the original selection (useful for creating "wrapping" functions, like
"<em>{selection}</em>"). {mark}- Remembers the current insertion point and restores it after the
sendkeyscall. Thus"<p>{mark}</p>"inserts<p></p>and leaves the insertion point between the tags.
$(elem).sendkeys('1234') inserts 1234,
$(elem).sendkeys('123{backspace}4') inserts 124, and
$(elem).sendkeys('1234{leftarrow}{leftarrow}{leftarrow}{del}') inserts 134, and
$(elem).sendkeys('<a href="{mark}">{selection}</a>') turns the current selection into the text of a hyperlink and leaves the insertion point in position to type the link itself.
I used Microsoft's key-escaping notation rather than backslashes because putting backslashes in strings means escaping them, and I always get lost in the forest of slashes. Unlike the Microsoft function, this does not use metacharacters (+^%~).
Options
There are no real options to the plugin call itself, but you can set your own special keys as the second parameter to the plugin: $(elem).sendkeys('abc{hello}', {'{hello}', function(rng) {alert('hello') }}), where rng is the range object returned by bililiteRange
The defaults are:
$.fn.sendkeys.defaults = {
'{backspace}': function (rng){...},
'{rightarrow}': function (rng){...},
'{leftarrow}': function (rng){...},
'{del}': function (rng){...},
'{{}': function(rng) {$.fn.sendkeys.defaults.simplechar (rng, '{')},
'{enter}': function (rng){...},
'simplechar': function (rng, s){...}// the default function to insert string s
};
You can create synonyms easily, like $.fn.sendkeys.defaults['{BS}'] = $.fn.sendkeys.defaults['{backspace}'].
$.fn.sendkeys.defaults.simplechar is the workhorse insertion function. You can define your own special key functions in the defaults object for all elements, and in the $.data cache for specific elements.
Thus, $.fn.sendkeys.defaults['{alert}'] = function(rng) {alert(rng.text()} and
$.data(elem, 'sendkeys', {
'{alert}' : function(rng) {alert(rng.text()}}
});
will use that function on that element only.
Events
To help simulate an actual keypress, the plugin does a trigger('keypress') with the event
keyCode, charCode and which all set to the ascii value of the letter sent. This is only triggered with simplechar; special characters do not trigger that event.
In addition, trigger('sendkeys') (a custom event) is executed, with event.which set to the original string that was sent.
$('.selectoutput').click(function(){
$('.output').removeClass('selected');
var index = $(this).parents('th').index();
$('.output').eq(index).addClass('selected');
});
$('div.test input:button').click(function(){
$('.output.selected').sendkeys($('div.test input:text').val());
});
$('div.wrap input:button').click(function(){
var tag = $('div.wrap select').val();
$('.output.selected').sendkeys('<'+tag+'>{selection}{mark}</'+tag+'>');
});
$('.phonepad input').click(function(){
$('.output.selected').sendkeys(this.name || this.value);
});
$('.output').bind('keypress', function(evt){
$('#keypress').text($('#keypress').text()+' '+evt.which);
}).bind('sendkeys', function(evt){
$('#sendkeys').text($('#sendkeys').text()+' '+evt.which);
}).bind('focus', function(){
var index = $(this).parents('td').index();
$('.selectoutput').eq(index).click();
});;
<div>
<table style="width: 100%" border="2" id="demo" >
<thead>
<tr>
<th><label>
<input type="radio" class="selectoutput" name="selectoutput" checked="checked" />
<code><input></code>
</label></th>
<th><label>
<input type="radio" class="selectoutput" name="selectoutput" />
<code><textarea></code>
</label></th>
<th><label>
<input type="radio" class="selectoutput" name="selectoutput" />
<code><div></code>
</label></th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" class="output selected" /></td>
<td><textarea class="output"></textarea></td>
<td><div class="output" contentEditable="true"></div></td>
</tr>
</tbody>
</table>
<div class="phonepad">
<input type="button" name="{leftarrow}" value="←"/><input type="button" name="{rightarrow}" value="→"/><input type="button" name="{backspace}" value="BS"/><input type="button" name="{selectall}" value="All"/><br/>
<input type="button" value="7" /><input type="button" value="8" /><input type="button" value="9" /><br/>
<input type="button" value="4" /><input type="button" value="5" /><input type="button" value="6" /><br/>
<input type="button" value="1" /><input type="button" value="2" /><input type="button" value="3" /><br/>
<input type="button" value="*" /><input type="button" value="0" /><input type="button" value="#" /><input type="button" name="{enter}" value="↵"/></div>
<div class="test"><input type="text" /><input type="button" value="test"/></div>
<div class="wrap"><select><option>em</option><option>strong</option><option>del</option></select><input type="button" value="Wrap Selection"/></div>
<p>The test button will send the entered string directly. The wrap button will wrap the current selection with the given tag and leave the insertion point after the selection (uses <code><tag>{selection}{mark}</tag></code>).</p>
<div id="keypress">keypress event.which:</div>
<div id="sendkeys">sendkeys event.which:</div>
</div>






{ 14 } Comments
I think the flakiness you’re experiencing with {enter} might be due to the difference in ASCII between a newline (ASCII code 13) and a carriage return (ASCII code 10). If you replace {enter} with ‘\r\n’ instead of just ‘\n,’ I think you’ll experience more consistent behavior across browsers.
Great work here!
@Brad
That may be part of it (when I get a change I’ll try it), but I think the issue is the inconsistency on how browsers handle returns in contenteditable, whether to split the <div< or to insert a <br>, and then how they handle editing across separate HTML elements (there’s no newline character inserted).
–Danny
Hi, many thanks for doing this, very useful. A problem I’ve noticed is that if you use your keypad to enter some value and then change the caret position using your real keyboard’s left or right keys, it no longer works properly (in IE). Is this a known issue and/or any suggestion of how to workaround it ?
@Mike:
I’m noticing that as I play with it now. I’m not sure what it’s doing. I’ll try to get a chance to look at it.
Danny
SelectAll doesn’t seem to emulate the behavior of Control A on Windows (Chrome). I’m testing this on Etherpad Lite.
Any idea?
@John McLear
It works for me in Chrome. The commands are case-sensitive; are you using {selectall}? Do you have a sample page with the error?
–Danny
Danny, if you are familiar w/ git doing a
git clone git://github.com/Pita/etherpad-lite.git
then
checkout feature/frontend-tests
then bin/run.sh
Will bring our testing framework live on http://127.0.0.1:9001/tests/frontend/
Simply uncomment the keystroke_urls_become_clickable.js in index.html and you can see the test working in real time. Visiting a pad on http://beta.etherpad.org and pressing control A shows the correct behavior for Select All.
@John McClear:
I’m not going to have the time to analyze this. It looks like you’re using a
content-editable<div> for your editor. You may have to look at my source code (which hopefully is reasonably understandable) to figure out what is going on.–Danny
Alrighty, ta anyways, will keep you posted on any other bugs I discover.
@John:
Based on your colleagues’ work, I’ve updated the code to work with iframes.
–Danny
sir,
when i try to use nicedit (http://nicedit.com/) with this sendkeys it is not working? how can i sendkeys to nicedit…. plz help….
mottusuchi
Hey man any chance of support for ‘tab’, ‘uparrow’ and ‘downarrow’ please? :)
Also how about moving this to github and support for ‘pageup’ & ‘pagedown’ ? :)
@John McLear:
I’ve never quite gotten my head around git/github, though I realize I really need to. The code is MIT licensed, so feel free to fork your own version and put it on GitHub. As for other keys, the plugin only manipulates a single element, so
sendkeys('\t')will insert a tab character. If you want to move the focus to the next element you’ll have to do that directly withelement.focus().I’ve though about uparrow/downarrow etc., but that would involve somehow knowing about the layout of the text and I can’t see how to do that. How many characters are in a line? If you have any ideas, please let me know.
–Danny
Post a Comment