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

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