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.

9 Comments

  1. prachi says:

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

  2. Danny says:

    @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 says:

    Yes it triggers input event …but not change event…

  4. Francesco Facco says:

    Your code is AMAZING!.. havent found anything like it anywhere else.. I only have one problem:
    I’m using it to create a virtual keyboard into a textarea. When I simulate the “left arrow” event, the insert point moves back (i.e. subsequent characters are inserted at point where I “reversed” to), but the blinking cursor remains at the end of text , so the “insert point” is not clear..
    Any help would be highly appreciated!

  5. Danny says:

    It seems to be working for me. Do you have publicly accessible website with the problem?

  6. Francesco Facco says:

    WOW thank you very much for the help offer!!!

    http://dev.delagarda.com/tu/

  7. Danny says:

    Francesco:
    You are using `sendkeys(“right”);` when you should be using `sendkeys(“{ArrowRight}”);`. Note that the commands need to enclosed in curly braces, and the name is `ArrowRight` or `rightarrow`, not `right`.

    Hope this helps.
    Danny

  8. Francesco Facco says:

    Thanks Danny… but I was looking at my “left” cursor.. I use :
    $(“#tbRequestGeneric”).trigger({type: ‘keydown’, key: “ArrowLeft”|| “←”});

    I changed it to :
    $(“#tbRequestGeneric”).sendkeys(“{ArrowLeft}”);
    But it still does the same thing… the “Insert point” moves (i.e. characters are getting inserted in the proper point), but the cursor stays at the end..
    I’ve been killing myself with this for 2 days now!!

  9. Francesco Facco says:

    What gets me crazy is that this page works just fine: I tried to pull it apart to make a working sample from it but there’s just too much other stuff on the page to do it. I wish there was a simple sample withoug all the extra html so I could work through it properly!

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75