Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save garyfeng/1ee0eea1012ce584f9e100c796a3b7ae to your computer and use it in GitHub Desktop.
Save garyfeng/1ee0eea1012ce584f9e100c796a3b7ae to your computer and use it in GitHub Desktop.
Keystroke logging implementation
<p style="width: 25%;">
Type some stuff in the box below. The grey output shows reconstituting the student's response, event by event, based on the diffs saved as observable events.<br />
<br />
An event is generated every time input occurs, but events are <em>logged</em> asynchronously every 50ms to help smooth out fast bursts of typing.
</p>
<textarea id="textinput" rows="10" columns="300" style="width: 25%;"></textarea>
<div id="results" style="width:50%;"></div>
# Demo of Keystroke Logging
# By Gary Feng, inspired a codepen by Lonnie Smith
# Copyleft, 2015
#
# This uses the JSDiff library to compare the text in the textarea, and shows the timestampe and diffs using simple inline css.
# In a real application you'd want to log the data in a structured way.
#
# standardize line endings; this may or may not be strictly necessary, but we do want to be sure that Unix- and Windows-type line endings are treated as the same, and are always written as Unix-style line endings per the data spec.
normalize = (str) -> str.replace(/(\r\n|\r|\n)/gm, '\n')
# In order to capture diffs between the text value of a field before and after a keystroke event, remember what the last entry was. In any real implementation, you'd have multiple textboxes to track, so a single global variable like this wouldn't work.
oldE = ""
# Here, we use a queue for log events, because users may be typing very rapidly. Events are generated as they are produced, and then placed into a queue. A loop dequeues 1 event every 50 ms and logs it. In the context of a more typical SBT implementation, it may be reasonable to immediately append the event the object representing task state, or it may be more effective to periodically attach events in batches. We'd want to make sure that when the platform polls for task data, that what it collects is not more than a second or so behind what the user is actually doing, so that it won't be possible to lose much data in the event of a system crash or similar.
queue = []
# Perform a diff. This method is fired on the input event and returns an object representing the difference between the current and previous state of the text input: {changed, position, removedText, addedText}
# garyfeng: using JsDiff
# http://dl.dropboxusercontent.com/u/36045409/diff.js
doDiff = (e) ->
newE = normalize(e.target.value)
rslt =
#newE: newE
#oldE: oldE
timestamp: (new Date()).toJSON()
diff: JsDiff.diffChars(oldE, newE)
patch: JsDiff.createPatch("t", oldE, newE)
# console.log(rslt)
oldE = newE
#logDiff2(rslt)
queue.push rslt
# demo reconstructing the current state of textbox based on diffs. This would be done post-administration during analysis and does NOT need to be part of any SBT implementation.
currentText = ""
patchDiff = (diff) ->
currentText = JsDiff.applyPatch(currentText, diff.patch)
return currentText
# display result of diff on screen. In an SBT implementation, this would add a diff to cached task state data. When the state data is sent to the platform via a StateInfo_Reply, it should comform to the data capture specification.
# garyfeng: using JsDiff.js
# https://github.com/kpdecker/jsdiff
# diffDiff = JsDiff.diffChars(one, other);
logDiff = (m) ->
str1="<p> <b>time:</b>#{m.timestamp}, <b>diff</b>:"
strend=", <b>reconstructed</b>: #{patchDiff(m)}</p>"
strdiff=""
#console.log(m.diff)
for part in m.diff
do (part)->
# green for additions, red for deletions
# grey for common parts
style = if part.added then 'color:blue; text-decoration: underline;' else if part.removed then 'color:red; text-decoration: line-through;' else 'color:grey;'
strdiff +="<span style=#{style}>#{part.value}</span>"
#console.log(strdiff)
$('#results').prepend str1+strdiff+strend
# unload cached events at regular intervals. See comment at top of file regarding the use of a queue in this example.
log = () ->
if queue.length > 0
logDiff queue.shift() # not at all efficient in JavaScript.
setTimeout(log, 50)
log()
# attach diff function to textarea. This example is bound to the input event, but, depending on the context, it may make more sense to bind to keypress or keyup or keydown.
# For example, in one application we were interested in tracking the key travel time, which is the time duration between the keydown and keyup of the same key. This requires slightly more logic to keep track.
$ () ->
$('#textinput').on('input', doDiff)
<script src="//dl.dropboxusercontent.com/u/36045409/diff.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment