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><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 contenteditable></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>
<td><div class="output" >This is not editable text</div></td>
</tr>
</tbody>
</table>
<div class="phonepad">
<input type="button" name="ArrowLeft" value="←"/><input type="button" name="ArrowRight" 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>
<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.