The $.fn.sendkeys
Plugin
This is now obsolete. sendkeys
is at version 4, and is documented at "Rethinking $.fn.sendkeys
".
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
See $.fn.sendkeys on github.
See bililiteRange on github.
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. Warning: In
contenteditable
elements,{enter}
is flaky and inconsistent across browsers. This is due to the flakiness ofcontenteditable
itself; I can't figure out what to do about this. {tab}
- Insert a
'\t'
character.$().sendkeys('\t')
would work just as well, but there are circumstances when I wanted to avoid having to escape backslashes. {newline}
- Insert a
'\n'
character, without the mangling that{enter}
does. {{}
- 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
sendkeys
call. 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 (+^%~).
Special keys
There are no real options to the plugin call itself, but you can set your own special keys in the bililiteRange.sendkeys
object. The defaults are:
bililiteRange.sendkeys = {
'{backspace}': function (rng){...},
'{rightarrow}': function (rng){...},
'{leftarrow}': function (rng){...},
etc.
};
rng
is the bililiteRange object being manipulated. The default action (for anything not in braces) is rng.text(string, 'end')
. You can create synonyms easily, like bililiteRange.sendkeys['{BS}'] = bililiteRange.sendkeys['{backspace}']
. And adding new functions is like bililiteRange.sendkeys['{selectall}'] = function(rng) {rng.bounds('all')}
. Using an undefined "specialkey" just inserts the name, so $(el).sendkeys('{foo}')
inserts 'foo'
. The original text of the range is stored in rng.data().sendkeysOriginalText
, so a way to show the selected text would be bililiteRange.sendkeys['{console}'] = function (rng) {console.log(rng.data().sendkeysOriginalText)
.
The jQuery plugin (but not the bililiteRange function) turns '\n'
into '{enter}'
in the input string. It can be escaped to just insert the newline by quoting it in braces: '{\n}'
. This mangling turned out to be less useful than I thought, but I kept it in for backwards compatibility.
bililiteRange
function
The jQuery plugin is just a wrapper for the bililiteRange(element).sendkeys(string)
function. The plugin is simply:
$.fn.sendkeys = function (x){
x = x.replace(/([^{])\n/g, '$1{enter}'); // turn line feeds into explicit break insertions, but not if escaped
return this.each( function(){
bililiteRange(this).bounds('selection').sendkeys(x).select();
this.focus();
});
}; // sendkeys
So the plugin manipulates the element (acting on the selection, then selecting the result), while the bililiteRange function affects the text but does not change the selection.
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').focus();
});
$('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').each(function(){
bililiteRange(this); // initialize the selection tracking
}).on('keypress', function(evt){
$('#keypress').text($('#keypress').text()+' '+evt.which);
}).on('sendkeys', function(evt){
$('#sendkeys').text($('#sendkeys').text()+' '+evt.which);
}).on('focus', function(){
var index = $(this).parents('td').index();
$('.output').removeClass('selected');
$('.output').eq(index).addClass('selected')
$('.selectoutput').eq(index).attr('checked',true);;
});
<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>
Brad Friedman says:
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!
August 5, 2011, 12:00 pmDanny says:
@Brad
August 5, 2011, 12:10 pmThat 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
Mike says:
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 ?
October 17, 2011, 10:17 pmDanny says:
@Mike:
October 18, 2011, 7:56 amI’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
John McLear says:
SelectAll doesn’t seem to emulate the behavior of Control A on Windows (Chrome). I’m testing this on Etherpad Lite.
Any idea?
October 3, 2012, 2:31 pmDanny says:
@John McLear
October 3, 2012, 2:56 pmIt works for me in Chrome. The commands are case-sensitive; are you using {selectall}? Do you have a sample page with the error?
–Danny
John McLear says:
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.
October 3, 2012, 3:00 pmDanny says:
@John McClear:
October 3, 2012, 3:04 pmI’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
John McLear says:
Alrighty, ta anyways, will keep you posted on any other bugs I discover.
October 3, 2012, 3:29 pmDanny says:
@John:
October 5, 2012, 9:28 amBased on your colleagues’ work, I’ve updated the code to work with iframes.
–Danny
mottusuchi says:
sir,
November 6, 2012, 8:45 pmwhen i try to use nicedit (http://nicedit.com/) with this sendkeys it is not working? how can i sendkeys to nicedit…. plz help….
mottusuchi
John McLear says:
Hey man any chance of support for ‘tab’, ‘uparrow’ and ‘downarrow’ please? :)
November 13, 2012, 4:14 amJohn McLear says:
Also how about moving this to github and support for ‘pageup’ & ‘pagedown’ ? :)
November 13, 2012, 4:23 amDanny says:
@John McLear:
November 13, 2012, 6:22 amI’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
Actually deleting the text from text fields by triggering the keypress event for backspaceCopyQuery CopyQuery | Question & Answer Tool for your Technical Queries,CopyQuery, ejjuit, query, copyquery, copyquery.com, android doubt, ios question, sql quer says:
[…] http://bililite.com/blog/2011/01/23/improved-sendkeys/ […]
October 19, 2013, 3:55 amKevin says:
It’s probably worth mentioning for anyone that finds this that Danny now has his code on github:
https://github.com/dwachss/bililiteRange
November 18, 2013, 9:46 amDanny says:
@Kevin:
November 18, 2013, 12:15 pmThanks for mentioning this; I guess my last comment a year ago is out of date.
–Danny
Hacking at 0300 : Cross-Browser Text Ranges and Selections says:
[…] Fixin’s for chili Improved sendkeys […]
December 30, 2013, 1:43 pmHacking at 0300 : Preserving the Insertion Point on Blur says:
[…] watch it in action, see the sendkeys demo. Enter text in each box, then click or tab between them or click the radio buttons above them to […]
January 6, 2014, 11:54 amAlex says:
Thank you so much for this great plugin. Helped me to deal with some strange problems
July 13, 2014, 6:05 pmTim Macfarlane says:
Hi Danny, jquery.sendkeys has been extremely useful for testing browser apps, so thank you! I’ve created an NPM module for it, please get in touch if you want to own it.
https://www.npmjs.com/package/jquery-sendkeys
January 8, 2015, 3:50 amhttps://github.com/featurist/jquery-sendkeys
Danny says:
@Tim:
Thanks. I don’t know much about node, but it looks like a good thing. Can you just fork the bililiteRange project (https://github.com/dwachss/bililiteRange) and submit a pull request?
If not, it’s fine to leave it at this and I will have to look at npm myself.
Of note, I am currently working on updating sendkeys and can hopefully make it “invisible”–have it intercept keydown events and just work, as:
$(element).trigger({type: 'keydown', key: 'Enter'});
I will update this post when I’m done.
–Danny
January 8, 2015, 9:00 am