source: trunk/autoquest-htmlmonitor/src/main/js/autoquest-htmlmonitor.js @ 1018

Last change on this file since 1018 was 999, checked in by pharms, 12 years ago
  • added support for further event types
  • added automatic sending of data after an interaction timeout
  • corrected encoding of Strings in JSON
File size: 26.7 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15/**
16 * AutoQUEST - HTML Monitor
17 *
18 * Description: This script records the interactions done by an user on an
19 * HTML-Website and sends them to a server. It does not register actions on
20 * Flash, Java, or other special inputs. This script is tested on Firefox
21 * 15.0.1.
22 *
23 * To insert it on your HTML-side, you need to write <script
24 * language="JavaScript" type="text/javascript" src="autoquest-htmlmonitor.js"></script> in the
25 * head and change the src-attribute to the location, you have chosen for this
26 * script.
27 *
28 * To change the recorded events, edit the action config array. If you want to change
29 * the server where the data is send to, rewrite the destination variable. The
30 * records are send to the server, JSON-formatted, if there are 10 inputs or the
31 * user changes/closes the site.
32 *
33 * Authors: Simon Leidenbach, Simon Reuss, Patrick Harms
34 *
35 * Version: 0.1
36 */
37
38/**
39 * the server to send the recorded data to
40 */
41var autoquestDestination;
42
43/**
44 * an ID that is more or less unique for the client
45 */
46var autoquestClientId;
47
48/**
49 * the maximum number of recorded events to be put into one package sent to the server
50 */
51var autoquestPackageSize = 10;
52
53/**
54 * this variable defines the tags for which event handling shall be added, as well as the
55 * event handling action to be monitored
56 */
57var autoquestActionConfig = [
58    { "tag": "a", "actions": [ "onclick",
59                               "onfocus" ] },
60    //{ "tag": "abbr", "actions": [  ] },
61    //{ "tag": "address", "actions": [  ] },
62    //{ "tag": "applet", "actions": [  ] },
63    { "tag": "area", "actions": [ "onclick",
64                                  "onfocus" ] },
65    //{ "tag": "article", "actions": [  ] },
66    //{ "tag": "aside", "actions": [  ] },
67    { "tag": "audio", "actions": [ "onplaying",
68                                   "onpause",
69                                   "ontimeupdate" ] },
70    { "tag": "b", "actions": [ "onclick" ] },
71    //{ "tag": "bdi", "actions": [  ] },
72    //{ "tag": "bdo", "actions": [  ] },
73    //{ "tag": "blockquote", "actions": [  ] },
74    { "tag": "body", "actions": [ "onbeforeunload",
75                                  "onload",
76                                  "onunload",
77                                  //"onerror",
78                                  "onscroll",
79                                  "onpagehide",
80                                  "onpageshow",
81                                  "onundo" ] },
82    { "tag": "button", "actions": [ "onclick",
83                                    "onfocus" ] },
84    { "tag": "canvas", "actions": [ "onclick" ] },
85    //{ "tag": "caption", "actions": [  ] },
86    { "tag": "cite", "actions": [ "onclick" ] },
87    { "tag": "code", "actions": [ "onclick" ] },
88    //{ "tag": "col", "actions": [  ] },
89    //{ "tag": "colgroup", "actions": [  ] },
90    { "tag": "command", "actions": [ "onclick",
91                                     "onfocus" ] },
92    //{ "tag": "datalist", "actions": [  ] },
93    { "tag": "dd", "actions": [ "onclick" ] },
94    { "tag": "del", "actions": [ "onclick" ] },
95    //{ "tag": "details", "actions": [  ] },
96    { "tag": "dfn", "actions": [ "onclick" ] },
97    { "tag": "div", "actions": [ "onclick" ] },
98    //{ "tag": "dl", "actions": [  ] },
99    { "tag": "dt", "actions": [ "onclick" ] },
100    { "tag": "em", "actions": [ "onclick" ] },
101    { "tag": "embed", "actions": [ "onclick" ] },
102    //{ "tag": "fieldset", "actions": [  ] },
103    //{ "tag": "figcaption", "actions": [  ] },
104    //{ "tag": "figure", "actions": [  ] },
105    //{ "tag": "footer", "actions": [  ] },
106    { "tag": "form", "actions": [ "onreset",
107                                  "onsubmit" ] },
108    //{ "tag": "header", "actions": [  ] },
109    //{ "tag": "hgroup", "actions": [  ] },
110    { "tag": "h1", "actions": [ "onclick" ] },
111    { "tag": "h2", "actions": [ "onclick" ] },
112    { "tag": "h3", "actions": [ "onclick" ] },
113    { "tag": "h4", "actions": [ "onclick" ] },
114    { "tag": "h5", "actions": [ "onclick" ] },
115    { "tag": "h6", "actions": [ "onclick" ] },
116    //{ "tag": "hr", "actions": [  ] },
117    { "tag": "i", "actions": [ "onclick" ] },
118    //{ "tag": "iframe", "actions": [  ] },
119    { "tag": "img", "actions": [ "onabort",
120                                 "onclick" ] },
121    { "tag": "input_text", "actions": [ "onchange",
122                                        "onfocus",
123                                        "onselect" ] },
124    { "tag": "input_password", "actions": [ "onchange",
125                                            "onfocus" ] },
126    { "tag": "input_checkbox", "actions": [ "onchange",
127                                            "onclick",
128                                            "onfocus" ] },
129    { "tag": "input_radio", "actions": [ "onchange",
130                                         "onclick",
131                                         "onfocus" ] },
132    { "tag": "input_submit", "actions": [ "onclick",
133                                          "onfocus" ] },
134    { "tag": "input_reset", "actions": [ "onclick",
135                                         "onfocus" ] },
136    { "tag": "input_file", "actions": [ "onclick",
137                                        "onfocus" ] },
138    { "tag": "input_image", "actions": [ "onclick",
139                                         "onfocus" ] },
140    { "tag": "input_button", "actions": [ "onclick",
141                                          "onfocus" ] },
142    { "tag": "input", "actions": [ "onchange",
143                                   "onfocus" ] },
144    { "tag": "ins", "actions": [ "onclick" ] },
145    { "tag": "kbd", "actions": [ "onclick" ] },
146    { "tag": "keygen", "actions": [ "onchange",
147                                    "onfocus" ] },
148    //{ "tag": "label", "actions": [  ] },
149    //{ "tag": "legend", "actions": [  ] },
150    { "tag": "li", "actions": [ "onclick" ] },
151    //{ "tag": "map", "actions": [  ] },
152    { "tag": "mark", "actions": [ "onclick" ] },
153    { "tag": "menu", "actions": [ "onclick" ] },
154    { "tag": "meter", "actions": [ "onclick" ] },
155    //{ "tag": "nav", "actions": [  ] },
156    //{ "tag": "noscript", "actions": [  ] },
157    { "tag": "object", "actions": [ "onclick" ] },
158    //{ "tag": "ol", "actions": [  ] },
159    //{ "tag": "optgroup", "actions": [  ] },
160    //{ "tag": "option", "actions": [  ] },
161    { "tag": "output", "actions": [ "onclick" ] },
162    { "tag": "p", "actions": [ "onclick" ] },
163    //{ "tag": "param", "actions": [  ] },
164    //{ "tag": "pre", "actions": [  ] },
165    { "tag": "progress", "actions": [ "onclick" ] },
166    { "tag": "q", "actions": [ "onclick" ] },
167    //{ "tag": "rp", "actions": [  ] },
168    //{ "tag": "rt", "actions": [  ] },
169    //{ "tag": "ruby", "actions": [  ] },
170    { "tag": "s", "actions": [ "onclick" ] },
171    { "tag": "samp", "actions": [ "onclick" ] },
172    //{ "tag": "section", "actions": [  ] },
173    { "tag": "select", "actions": [ "onchange",
174                                    "onfocus" ] },
175    { "tag": "small", "actions": [ "onclick" ] },
176    //{ "tag": "source", "actions": [  ] },
177    { "tag": "span", "actions": [ "onclick" ] },
178    { "tag": "strong", "actions": [ "onclick" ] },
179    //{ "tag": "sub", "actions": [  ] },
180    //{ "tag": "summary", "actions": [  ] },
181    //{ "tag": "sup", "actions": [  ] },
182    //{ "tag": "table", "actions": [  ] },
183    //{ "tag": "tbody", "actions": [  ] },
184    { "tag": "td", "actions": [ "onclick" ] },
185    { "tag": "textarea", "actions": [ "onchange",
186                                      "onfocus",
187                                      "onselect" ] },
188    //{ "tag": "tfoot", "actions": [  ] },
189    { "tag": "th", "actions": [ "onclick" ] },
190    //{ "tag": "thead", "actions": [  ] },
191    { "tag": "time", "actions": [ "onclick" ] },
192    //{ "tag": "tr", "actions": [  ] },
193    //{ "tag": "track", "actions": [  ] },
194    { "tag": "u", "actions": [ "onclick" ] },
195    //{ "tag": "ul", "actions": [  ] },
196    { "tag": "var", "actions": [ "onclick" ] },
197    { "tag": "video", "actions": [ "onplaying",
198                                   "onpause",
199                                   "ontimeupdate" ] },
200    //{ "tag": "wbr", "actions": [  ] },
201];
202
203/**
204 * a possibility to trace, what is going on
205 */
206var autoquestDoLog = false;
207
208/*var matchedTags = ["A", "ABBR", "ACRONYM", "ADDRESS", "AREA", "B", "BIG", "BLOCKQUOTE", "BODY",
209                   "BUTTON", "CAPTION", "CENTER", "CITE", "CODE", "COL", "COLGROUP", "DD", "DEL",
210                   "DFN", "DIR", "DIV", "DL", "DT", "EM", "FIELDSET", "FORM", "H1", "H2", "H3",
211                   "H4", "H5", "H6", "HR", "I", "IMG", "INPUT", "INS", "KBD", "LABEL", "LEGEND",
212                   "LI", "LINK", "MAP", "MENU", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL",
213                   "OPTGROUP", "OPTION", "P", "PRE", "Q", "S", "SAMP", "SELECT", "SMALL", "SPAN",
214                   "STRIKE", "STRONG", "SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT",
215                   "TH", "THEAD", "TR", "TT", "U", "UL", "VAR"];*/
216/*var actions = ['onclick', 'ondblclick', 'onkeypress', 'onkeydown', 'onkeyup',
217'onmouseout' , 'onmousemove' ,'onfocus','onscroll'];  // edit*/
218
219/**
220 * stores events, which were recorded but not sent to the server yet
221 */
222var autoquestRecordedEvents = [];
223
224/**
225 * stores the interval for sending data of inactive browser windows
226 */
227var autoquestSendInterval;
228
229/**
230 * automatically executed to initialize the event handling
231 */
232(function() {
233    initEventHandling();
234}());
235
236
237/**
238 * initializes the event handling after the document is loaded completely
239 */
240function initEventHandling() {
241    if (document.body) {
242        log("adding event handling attributes");
243        determineDestination();
244        addEventHandlingAttributes(document.documentElement, "");
245       
246        if (document.readyState !== "complete") {
247            // if the document is not loaded yet, try to add further event handling later
248            setTimeout(initEventHandling, 200);
249        }
250        else if (!autoquestSendInterval) {
251            // recall sending data each 100 seconds to ensure, that for browser windows staying
252            // open the data will be send, as well.
253            autoquestSendInterval = setTimeout(sendRequest, 100000);
254        }
255    }
256    else {
257        setTimeout(initEventHandling, 200);
258    }         
259}
260
261/**
262 * traverses the DOM-structure of the HTML-site and determines the URL of this script. Based on
263 * this URL, it calculates the destination to which the traced interactions must be sent
264 */
265function determineDestination() {
266    var scriptElements = document.getElementsByTagName("script");
267    var i;
268    var index;
269   
270    for (i = 0; i < scriptElements.length; i++) {
271        if ((scriptElements[i].type === "text/javascript") && (scriptElements[i].src)) {
272            index = scriptElements[i].src.lastIndexOf("script/autoquest-htmlmonitor.js");
273            if (index > -1) {
274                autoquestDestination = scriptElements[i].src.substring(0, index - 1);
275                log("using destination " + autoquestDestination);
276            }
277        }
278    }
279}
280
281/**
282 * traverses the DOM-structure of the HTML-site and adds event handling attributes to each
283 * relevant node
284 *
285 * @param node       the node of the DOM structure that shall be adapted and whose children shall
286 *                   be traversed
287 * @param parentPath the path to the parent node of the provided node within the DOM-structure of
288 *                   the HTML-site
289 */
290function addEventHandlingAttributes(node, parentPath) {
291    var nodePath;
292    var i;
293    var k;
294    var value;
295   
296    if (node.nodeType === Node.ELEMENT_NODE) {
297        nodePath = getNodePath(node, parentPath);
298       
299        for (i = 0; i < autoquestActionConfig.length; i++) {
300            if (getTagName(node) === autoquestActionConfig[i].tag.toLowerCase()) {
301                for (k = 0; k < autoquestActionConfig[i].actions.length; k++) {
302                    value = "handleEvent(this, '" +
303                                         autoquestActionConfig[i].actions[k] + "', '" +
304                                         nodePath + "', event);";
305
306                    if (!node.getAttribute(autoquestActionConfig[i].actions[k])) {
307                        node.setAttribute(autoquestActionConfig[i].actions[k], value);
308                    }
309                    else {
310                        var oldValue = node.getAttribute(autoquestActionConfig[i].actions[k]);
311                        if (oldValue.indexOf(value) < 0) {
312                            node.setAttribute(autoquestActionConfig[i].actions[k],
313                                              value + ' ' + oldValue);
314                        }
315                    }
316                }
317            }
318        }
319       
320        for (i = 0; i < node.childNodes.length; i++) {
321            addEventHandlingAttributes(node.childNodes[i], nodePath);
322        }
323    }
324}
325
326/**
327 * generates a path through the DOM-structure of the HTML-site depending on a node and the path
328 * to its parent node. The result is the parent path plus a path element for the provided node.
329 * The first part of the path element generated for the node is the tag name returned by
330 * {@link #getTagName(node)}. If the node has an id, it becomes the second part of the path
331 * element. If the node does not have an id, the method calculates the index of the node within
332 * all children of the same type within the parent node. This index becomes then the second part
333 * of the path element generated for the node.
334 *
335 * @param node       the node of the DOM structure for which the path shall be created
336 * @param parentPath the path to the parent node of the provided node
337 *
338 * @returns a path in the DOM-structure of the HTML-site including the parent path an a path
339 *          element for the provided node
340 */
341function getNodePath(node, parentPath) {
342    var nodePath = parentPath + "/";
343    var index = -1;
344    var i = 0;
345   
346    nodePath += getTagName(node);
347   
348    if ((node.id) && (node.id !== "")) {
349        nodePath += "(id=" + node.id + ")";
350    }
351    else {
352        if (node.parentNode) {
353            for (i = 0; i < node.parentNode.childNodes.length; i++) {
354                if (node.parentNode.childNodes[i].tagName === node.tagName) {
355                    index++;
356                    // if === also returns true if the nodes are not identical but only equal,
357                    // this may fail.
358                    if (node.parentNode.childNodes[i] === node) {
359                        break;
360                    }
361                }
362            }
363           
364        }
365        else {
366            index = 0;
367        }
368       
369        nodePath += "[" + index + "]";
370    }
371   
372    return nodePath;
373}
374
375/**
376 * handles an event that happened on a node. This method is called by the event handling attributes
377 * of the nodes. These attributes are generated by the
378 * {@link #addEventHandlingAttributes(node,parentPath)} function. It creates a new Event object and
379 * adds it to the list of <code>autoquestRecordedEvents</code>. If this list achieves the maximum
380 * <code>autoquestPackageSize</code> the events in the list are sent to the server asynchronously
381 * through calling {@link #sendRequest()}.
382 *
383 * @param node      the node that fired the event
384 * @param eventName the name of the event, e.g. onscroll
385 * @param nodePath  the path to the node in the HTML DOM on which the event occurred
386 * @param event     the HTML event that occured
387 */
388function handleEvent(node, eventName, nodePath, event) {
389    var eventType;
390    var eventObj = null;
391    var tagName;
392
393    if (!autoquestDestination) {
394        // do nothing if we have no destination to send data to
395        return;
396    }
397   
398    log("handling event " + eventName + " on " + node);
399   
400    eventType = eventName.toLowerCase();
401
402    if (autoquestRecordedEvents.length > 0) {
403        eventObj = autoquestRecordedEvents[autoquestRecordedEvents.length - 1];
404
405        // check if an event showed up several times either for the same or for a parent GUI element
406        if ((eventObj.type === eventName) && (eventObj.nodePath.indexOf(nodePath) === 0)) {
407            // the event is of the same type.
408            if (eventObj.nodePath.length > nodePath.length) {
409                // the same event showed up for the parent GUI element. This must not be handled.
410                // So ignore it
411                log("discarding event " + eventName + " on " + node +
412                    " as it is already handled by a child");
413                return;
414            }
415            else if (eventName !== "onscroll") {
416                // we have the same event on the same element. If it is an onscroll, we should
417                // reuse it. But it is not an onscroll. So we ignore the existing event.
418                eventObj = null;
419            }
420        }
421        else {
422            // the event is not of an equal type as the previous one. So we will not reuse it
423            eventObj = null;
424        }
425    }
426   
427    if (!eventObj) {
428        // create a new event and add it to the list
429        eventObj = new Event(eventType, nodePath);
430        log("storing event " + eventName);
431        autoquestRecordedEvents.push(eventObj);
432    }
433
434    // now add further event parameters
435    if ((eventType === "onclick") || (eventType === "ondblclick")) {
436        eventObj.setClickCoordinates(getEventCoordinates(event));
437    }
438
439    tagName = getTagName(node);
440   
441    if ("input_password" !== tagName) {
442        if ((eventType === "onkeypress") ||
443            (eventType === "onkeydown") ||
444            (eventType === "onkeyup"))
445        {
446            eventObj.setKey(event.keyCode);
447        }
448        else if (eventType == "onchange") {
449            if ((tagName.indexOf("input") === 0) ||
450                (tagName === "textarea") ||
451                (tagName === "keygen"))
452            {
453                eventObj.setSelectedValue(node.value);
454            }
455            else if (tagName === "select") {
456                eventObj.setSelectedValue(node.options.item(node.options.selectedIndex));
457            }
458        }
459    }
460   
461    if (eventType === "onscroll") {
462        eventObj.setScrollPosition(getScrollCoordinates(node));
463    }
464
465    if (autoquestRecordedEvents.length >= autoquestPackageSize) {
466        log("initiating sending events");
467        setTimeout(sendRequest, 100);
468    }
469    else if ((eventType === "onunload") || (eventType === "onbeforeunload")) {
470        log("initiating sending events");
471        sendRequest();
472    }
473
474}
475
476/**
477 * determines a tag name of a node. If the node is an input element, the tag name includes the
478 * type of element. Otherwise the method simply returns the tag name.
479 *
480 * @param node the node for which the name must be determined
481 *
482 * @return the name as described
483 */
484function getTagName(node) {
485    var tagName = null;
486   
487    if (node.tagName) {
488        tagName = node.tagName.toLowerCase();
489        if ("input" === tagName) {
490            if (node.type && (node.type !== "")) {
491                tagName += "_" + node.type;
492            }
493            else {
494                tagName += "_text";
495            }
496        }
497    }
498
499    return tagName;
500}
501   
502/**
503 * sends the collected data to the server, named in the destination-variable. For this it generates
504 * a JSON formatted message and uses Ajax and the <code>autoquestDestination</code> to send it to
505 * the server
506 */
507function sendRequest() {
508    var eventList;
509    var message;
510    var clientId;
511    var i = 0;
512    var request;
513   
514    if (autoquestRecordedEvents.length > 0) {
515        log("creating message");
516        eventList = autoquestRecordedEvents;
517       
518        // put the last event into the new list to allow for checks for reoccurence of the same
519        // event
520        autoquestRecordedEvents = [ eventList.pop() ];
521       
522        message = "{\"message\":{\"clientInfos\":{";
523       
524        log("reading client id");
525        clientId = getClientId();
526        if ((clientId) && (clientId !== "")) {
527            message += "\"clientId\":\"" + clientId + "\",";
528        }
529       
530        log("adding other infos");
531        message += "\"userAgent\":\"" + navigator.userAgent + "\",";
532        message += "\"title\":\"" + document.title + "\",";
533        message += "\"url\":\"" + document.URL + "\"},";
534       
535       
536        message += "\"events\":[";
537       
538        for (i = 0; i < eventList.length; i++) {
539            if (i > 0) {
540                message += ",";
541            }
542            message += eventList[i].toJSON();
543        }
544       
545        message += "]}}";
546       
547        request = null;
548       
549        // Mozilla
550        if (window.XMLHttpRequest) {
551            request = new XMLHttpRequest();
552        }
553        // IE
554        else if (window.ActiveXObject) {
555            request = new ActiveXObject("Microsoft.XMLHTTP");
556        }
557       
558        request.open("POST", autoquestDestination, false);
559        request.setRequestHeader("Content-Type", "application/json");
560
561        log("sending " + message);
562        request.send(message);
563    }
564}
565
566/**
567 * determines the scroll coordinates of the scrolled element
568 *
569 * @param node the element that was scrolled
570 *
571 * @returns the coordinates of the scrolling as an array with x and y coordinate
572 */
573function getScrollCoordinates(node) {
574    if (node.scrollLeft || node.scrollTop) {
575        return [node.scrollLeft, node.scrollTop];
576    }
577    else if ((node === window) && window.pageYOffset) {
578        return [window.pageXOffset, window.pageYOffset];
579    }
580    else if ((node == document) || (node == document.body) || (node == document.documentElement)) {
581        if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
582            return [document.body.scrollLeft, document.body.scrollTop];
583        }
584        else if (document.documentElement &&
585                 (document.documentElement.scrollLeft || document.documentElement.scrollTop))
586        {
587            return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
588        }
589    }
590
591    return [-1, -1];
592}
593
594/**
595 * determines the coordinates of an onclick or ondblclick event. If the coordinates to not come
596 * with the provided event, they are determined based on the surrounding object
597 *
598 * @param event the event to extract the coordinates of
599 *
600 * @returns the coordinates of the event as an array with x and y coordinate
601 */
602function getEventCoordinates(event) {
603    if (event.layerX) {
604        return [event.layerX, event.layerY];
605    }
606    else if (event.offsetX) {
607        return [event.offsetX, event.offsetY];
608    }
609
610    var obj = event.target || event.srcElement;
611    var objOffset = getPageOffset(obj);
612
613    return [(event.clientX - objOffset[0]), (event.clientY - objOffset[1])];
614}
615
616/**
617 * determines the page offset of an object using the parent objects offset
618 */
619function getPageOffset(object) {
620    var top = 0;
621    var left = 0;
622    var obj = object;
623
624    while (obj.offsetParent) {
625        left += obj.offsetLeft;
626        top += obj.offsetTop;
627        obj = obj.offsetParent;
628    }
629
630    return [left, top];
631}
632
633/**
634 * generates a client id based on several information retrieved from the environment. The client
635 * id is not always unique
636 *
637 * @returns the client id
638 */
639function getClientId() {
640    var clientIdStr;
641    var clientId;
642    var i = 0;
643   
644    if (!autoquestClientId) {
645        // create something like a more or less unique checksum.
646        clientIdStr =
647            navigator.appCodeName + navigator.appName + navigator.appVersion +
648            navigator.cookieEnabled + navigator.language + navigator.platform +
649            navigator.userAgent + navigator.javaEnabled() + window.location.protocol +
650            window.location.host + new Date().getTimezoneOffset();
651
652        clientId = clientIdStr.length;
653
654        for (i = 0; i < clientIdStr.length; i++) {
655            clientId += clientIdStr.charCodeAt(i);
656        }
657       
658        autoquestClientId = clientId;
659    }
660
661    return autoquestClientId;
662}
663
664/**
665 * performs a simple logging by adding a specific div to the HTML
666 *
667 * @param text the text to be logged
668 */
669function log(text) {
670    if (autoquestDoLog) {
671        var loggingInfo = document.getElementById("loggingInfoParagraph");
672       
673        if (!loggingInfo) {
674            var div = document.createElement("div");
675            div.setAttribute("style", "z-index:1000000; background-color:#afa; " +
676                             "border:1px black solid; position:absolute; " +
677                             "top:10px; right:10px; width:350px; height:auto");
678           
679            loggingInfo = document.createElement("div");
680            loggingInfo.id = "loggingInfoParagraph";
681            div.appendChild(loggingInfo);
682           
683            var body = document.getElementsByTagName("body")[0];
684            if (!body) {
685                alert("could not enable logging");
686            }
687            else {
688                body.appendChild(div);
689            }
690        }
691       
692        loggingInfo.appendChild(document.createTextNode(text));
693        loggingInfo.appendChild(document.createElement("br"));
694    }
695}
696
697/**
698 * this class represents a single event
699 *
700 * @param type     the type of the event
701 * @param nodePath the path through the HTML DOM to the event target
702 */
703function Event(type, nodePath) {
704    this.type = type;
705    this.nodePath = nodePath;
706    this.time = new Date().getTime();
707   
708    this.setClickCoordinates = function(coordinates) {
709          this.clickCoordinates = coordinates;
710    };
711   
712    this.setKey = function(key) {
713          this.key = key;
714    };
715   
716    this.setSelectedValue = function(value) {
717        this.selectedValue = encodeText(value);
718    };
719 
720    this.setScrollPosition = function(scrollPosition) {
721        this.scrollPosition = scrollPosition;
722    };
723   
724    this.toJSON = function() {
725        var eventInJSON =
726            "{\"path\":\"" + this.nodePath + "\",\"time\":" + this.time + ",\"eventType\":\"" +
727            this.type + "\"";
728
729        if ((this.clickCoordinates) && (!isNaN(this.clickCoordinates[0]))) {
730            eventInJSON += ",\"coordinates\":[" + this.clickCoordinates + "]";
731        }
732
733        if (this.key) {
734            eventInJSON += ",\"key\":" + this.key;
735        }
736       
737        if (this.selectedValue) {
738            eventInJSON += ",\"selectedValue\":\"" + this.selectedValue + "\"";
739        }
740       
741        if ("onscroll" === this.type) {
742            eventInJSON += ",\"scrollPosition\":[" + this.scrollPosition + "]";
743        }
744
745        eventInJSON += "}";
746       
747        return eventInJSON;
748    };
749   
750    function encodeText(text) {
751        return text.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"");
752    };
753}
754
Note: See TracBrowser for help on using the repository browser.