-
Notifications
You must be signed in to change notification settings - Fork 0
Keyboard and Clipboard Events
The following was last updated for v0.2, the current master
branch of MathQuill. Much will (hopefully) change in v0.3.
The quick-and-dirty: fake keyboard events bubble in the math DOM, keypress
remembers the last keydown
event and does setTimeout
to check textarea contents, the selection is selected in the textarea ready to be cut or copied
(This document assumes knowledge of at least the v0.2 Architecture Overview and Events sections.)
Thanks to browser inconsistencies, getting keyboard events to work for normal and special keys, for both a tap and holding down, is stupidly hard. We provide an abstraction that is cross-browser but doesn't use browser-sniffing to hide these inconsistencies from event handlers in the math tree that actually deal with math editing.
Getting clipboard actions (cut/copy/paste) to work in MathQuill seems like a simple matter of having the LaTeX of the currently selected math be selected in the textarea, but due to an obscure lag in Chrome, we have to throttle how often we set the textarea.
In some old version of Chrome there was major lag when setting the contents of a textarea. It seems to be fixed now and it's impossible to get old versions of Chrome to try to repro, so pending further confirmation that this bug can't bite us again, we might go back to the naive solution of constantly setting the contents of the textarea to be what we want.
This is a lag of milliseconds, so it's only a problem when we let it compound many, many times, and is solved by throttling how often we set the contents of the textarea.
- When the selection in the math editable textbox changes,
- we set a timeout, unless there is already such a timeout set,
- for a function
setTextareaSelection
that will- set the contents of the textarea to the LaTeX of the currently selected math,
- and select the contents of the textarea.
So, if a whole bunch of changes happen in one thread, only one timeout will be set, and when it runs, it will set the contents of the textarea to the end result.
But now there's the possibility of a race condition where the user edits and then cuts/copies/pastes before the timeout runs and changes the textarea appropriately, so in browsers that support them, we listen for the cut
/copy
/paste
events. Like keydown
/keypress
, they run just before the event's action happens, but unlike keydown
/keypress
, you can't cancel the action. So on cut
/copy
, we setTextareaSelection()
.
On paste
, we set the pasting
flag (see Keyboard Events, below) and, on a timeout waiting for after the paste happens, we writeLatex()
the contents of the textarea (with a hack special casing pasting into a RootTextBlock
), clear the textarea and unset the pasting
flag.
According to this excellent resource, the keyboard events browser inconsistencies affecting this project are as follows:
- In most browsers,
- it's only possible to reliably tell special keys (like arrow keys, backspace, tab etc) apart from text entry with the
keydown
event object- e.g. on
keypress
the keycodes for the left, up, right and down arrow keys are identical to that of%
,&
,'
and(
, respectively
- e.g. on
- but it's only possible to reliably tell what character was typed on
keypress
- in fact even the jQuery-normalized
event.which
onkeypress
is unreliable, for example on the French keyboard the backslash\
(crucial to MathQuill) is typed with Ctrl+Alt+8, which for all a web app can tell is a browser or OS shortcut #11 - we ended up waiting after
keypress
and seeing what text was entered into the textarea
- in fact even the jQuery-normalized
- it's only possible to reliably tell special keys (like arrow keys, backspace, tab etc) apart from text entry with the
- Unfortunately,
- some browsers, for special keys, only fire
keydown
- some browsers, when you hold down a key, only repeatedly fire
keypress
- some browsers, for special keys, only fire
We provide an abstraction that is cross-browser but doesn't use browser-sniffing to hide these inconsistencies from "higher-level" handlers on math DOM nodes that actually deal with text entry and backspaces and stuff.
Our cross-browser algorithm is to record keydown
events and on keypress
, if there's been no keydown
since the last keypress
of the same kind, replay the recorded one. That way special keys can be handled on keydown
without worry that it doesn't fire repeatedly when you hold down special keys. Text entry is handled by checking the contents of the textarea after keypress
, so there are separate higher-level math DOM event handlers for keydown
and textInput
.
- The actual event handlers bound to the jQuery object (and hence, the HTML DOM element) of the root math element share a closured
lastKeydn
object.- on
keydown
,- store the event object and set the flag before triggering
keydown
in the math DOM
- store the event object and set the flag before triggering
- on
keypress
,- check the flag
- if there's been no
keydown
since the lastkeypress
,- It's possible for a
keypress
to fire without akeydown
firing first if the textarea is given focus during thekeydown
of a different focusable element, i.e. by that element'skeydown
event handler, so check if thiskeypress
is the same kind as the last- if not, ignore this
keypress
- if so, trigger
keydown
in the math DOM
- if not, ignore this
- It's possible for a
- if there's been no
- in case we're in the race condition where the user just started editing the math and the timeout of
setTextareaSelection()
hasn't run yet, clear the timeout - now
setTimeout(textInput)
to wait after thekeypress
event has been handled by the browser, at which point- check the
pasting
flag: if set, that would mean apaste
event fired but the timeout waiting til after hasn't run yet, in which case, the textarea will be nonempty but because it was pasted in, not because text has been entered in, so ignore (the timeout set in thepaste
event handler will paste in the text). See also 68d9d1a0 - check if there's anything selected in the textarea: if there is, then it isn't possible that text was entered, for that would overwrite the selection, so ignore. This happens in browsers where pressing Ctrl+C triggers
keypress
, and the user had something selected that they intended to copy. We don't want to interpret that selection, which is the LaTeX of the currently selected math (see above), as text they entered. See also b1318e5.- This is kind of a lie. I actually only check if there's anything selected in non-IE <9, since, as far as I've researched, IE <9 doesn't fire
keypress
at unwelcome times. If anything like #40 or #71 is reported in IE <9, revert af4c280
- This is kind of a lie. I actually only check if there's anything selected in non-IE <9, since, as far as I've researched, IE <9 doesn't fire
- see if text has been entered into the textarea, and if so,
- trigger
textInput
in the math DOM, - set
textareaSelectionTimeout
toundefined
: we've cleared thesetTextareaSelection
timeout, so it will never run, but future changes to the selection will checktextareaSelectionTimeout
to throttle calls and if it isn'tundefined
, those checks will result in the mistaken belief that there's asetTextareaSelection
timeout that will run soon
- trigger
- else if not,
- check if
textareaSelectionTimeout
isundefined
, and if so, the timeout must've been cleared in thekeypress
handler, and no text was typed overwriting the selection, so nowsetTextareaSelection()
- check if
- check the
- check the flag
- on
- The
.keydown()
and.textInput()
event handlers inherited by all MathElements fromMathElement.prototype
just tail call the parent'skeydown
/textInput
handler (Note: In the new architecture, instead of explicitly tail calling the parent's event handler, events are bubbled by the event triggerer and to cancel bubbling, the event handler explicitly returnsfalse
)- The root math block overrides its
keydown
andkeypress
event handlers to call the cursor's various.write()
and.backspace()
etc methods - Since the event bubbles up starting from the cursor's parent,
MathElement
s likeLatexCommandInput
can override theirkeydown
andtextInput
event handlers to do stuff when the cursor is in them and the user types something- Such
MathElement
key event handlers that don't want to cancel the bubbling of the event are required to explicitly fake the bubbling by calling the key event handlers of their parent MathElement- They can cancel the bubbling of the event by simply not calling their parent's event handlers
- We want the same events to bubble out of the math textboxes as would bubble out of a normal textarea, so we never return false or stop propagation on any of the textarea events, but we often want to prevent the default action, so the
.keydown()
event handler can return false to prevent default. Like normal event handlers, returning other falsy values does not work, it has to=== false
- Such
- The root math block overrides its
Tested (and confirmed working) on Mac for Safari 4, 5, Opera 10.5, Chrome 5+, Camino 2, Firefox 3.6, 4, even OmniWeb 5.
Tested (and confirmed working) on Windows for IE8.