Skip to content

Improved sendkeys

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 of contenteditable 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.
So $(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>&lt;input&gt;</code>
				</label></th>
				<th><label>
					<input type="radio" class="selectoutput" name="selectoutput" />
					<code>&lt;textarea&gt;</code>
				</label></th>
				<th><label>
					<input type="radio" class="selectoutput" name="selectoutput" />
					<code>&lt;div&gt;</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="&larr;"/><input type="button" name="{rightarrow}" value="&rarr;"/><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="&crarr;"/></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>&lt;tag&gt;{selection}{mark}&lt;/tag&gt;</code>).</p>
<div id="keypress">keypress event.which:</div>
<div id="sendkeys">sendkeys event.which:</div>
</div>

{ 19 } Comments


  1. Fatal error: Uncaught Error: Call to undefined function ereg() in /home/public/blog/wp-content/themes/barthelme/functions.php:178 Stack trace: #0 /home/public/blog/wp-content/themes/barthelme/comments.php(34): barthelme_commenter_link() #1 /home/public/blog/wp-includes/comment-template.php(1469): require('/home/public/bl...') #2 /home/public/blog/wp-content/themes/barthelme/single.php(44): comments_template() #3 /home/public/blog/wp-includes/template-loader.php(74): include('/home/public/bl...') #4 /home/public/blog/wp-blog-header.php(19): require_once('/home/public/bl...') #5 /home/public/blog/index.php(17): require('/home/public/bl...') #6 {main} thrown in /home/public/blog/wp-content/themes/barthelme/functions.php on line 178