Skip to content

Instantly share code, notes, and snippets.

@garyfeng
Created September 23, 2015 15:25
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/a5a0634549515cdfe256 to your computer and use it in GitHub Desktop.
Save garyfeng/a5a0634549515cdfe256 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>
# 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 scalar 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.replace(/ /,"&nbsp;")}</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.
$ () ->
$('#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>
@garyfeng
Copy link
Author

Forked from my initial anonymous pen, fixing the bug where soft spaces are not displayed correctly. Replacing all soft spaces with hard spaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment