Skip to content

Rethinking $.fn.sendkeys

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 user-initiated events. These are called "trusted events", 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).

Fortunately, bililiteRange and especially bililiteRange.sendkeys 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.
Just include the source code and keydown events get a new default handler (so it can be cancelled by preventDefault) that looks at the key field. 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 sendkeys command like ArrowLeft and is sent as sendkeys('{'+key+'}').
I used the modern Event.key rather than Event.which, so I don't have to translate keyCodes. If you need to use the old way, see my keymap plugin.

Thus now, $('textarea').trigger({type: 'keydown', key: 'A'}) will work as expected, as will $('textarea').trigger({type: 'keydown', key: 'Backspace'}).

The actual plugin

Under the hood, this uses a very simple jQuery plugin that just calls bililiteRange.sendkeys(). It also turns '\n' in the string into '{Enter}', which I thought would be useful but has actually not turned out that way. Putting the '\n' in braces ('{\n}' prevents the replacement.
The plugin itself is:

$.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();
  });
};



Demo


$('.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').trigger({type: 'keydown', key: this.name || this.value});
});
$('.output').each(function(){
	bililiteRange(this); // initialize the selection tracking
}).on('keydown', function(evt){
	if ($('#overridepad').is(':checked')){
		alert (evt.key);
		evt.preventDefault();
	}
}).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 contenteditable&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>
				<td><div class="output" >This is not editable text</div></td>
			</tr>
		</tbody>
	</table>
<div class="phonepad">
<input type="button" name="ArrowLeft" value="&larr;"/><input type="button" name="ArrowRight" 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>
<label>Alert on keydown event: <input type=checkbox id=overridepad /></label>
<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>

<div id="keypress">keypress event.which:</div>
<div id="sendkeys">sendkeys event.which:</div>
</div>
<div style="clear:both" />

The phone pad keys use $().trigger({type: 'keydown', key: key}). The test button does $().sendkeys(textbox.value). The wrap button does $().sendkeys('<tag>{selection}{mark}</tag>'). Note that the trigger code does not affect the non-editable DIV, while sendkeys does.
The "Alert on keydown event" checkbox attaches a handler to the keydown event which calls event.preventDefault, showing that the text entry and keypress events do not occur.

{ 3 } Comments

  1. prachi | September 29, 2015 at 8:38 am | Permalink

    Your plugin doesnt work……its not simulating key press,when you tab from one box to another it doesnt fire onchange event

  2. Danny | September 30, 2015 at 9:08 am | Permalink

    @prachi:
    The plugin does not really simulate a key press. It inserts a character into an editable, which means that it only simulates printing keys. sendkeys('\t') inserts a tab character; it does not move from one box to another. I haven’t tested whether using sendkeys then tabbing triggers the change event, but it does trigger the input event; I use that all the time.
    Hope this helps,
    Danny

  3. prachi | October 1, 2015 at 3:56 am | Permalink

    Yes it triggers input event …but not change event…

Post a Comment

Your email is never published nor shared. Required fields are marked *