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

Last change on this file since 1022 was 1022, checked in by pharms, 12 years ago
  • removed todos
File size: 31.5 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 the structure of the GUI of the current page
221 */
222var autoquestGUIModel;
223
224/**
225 * stores events, which were recorded but not sent to the server yet
226 */
227var autoquestRecordedEvents = [];
228
229/**
230 * stores the interval for sending data of inactive browser windows
231 */
232var autoquestSendInterval;
233
234/**
235 * automatically executed to initialize the event handling
236 */
237(function() {
238    initEventHandling();
239}());
240
241
242/**
243 * initializes the event handling after the document is loaded completely
244 */
245function initEventHandling() {
246    if (document.body) {
247        if (document.readyState !== "complete") {
248            // if the document is not loaded yet, try to add further event handling later
249            setTimeout(initEventHandling, 200);
250        }
251        else if (!autoquestSendInterval) {
252            log("adding event handling attributes");
253            determineDestination();
254            autoquestGUIModel =
255                addEventHandlingAndGetJSONRepresentation(document.documentElement, "");
256           
257            // recall sending data each 100 seconds to ensure, that for browser windows staying
258            // open the data will be send, as well.
259            autoquestSendInterval = setTimeout(sendRequest, 100000);
260        }
261    }
262    else {
263        setTimeout(initEventHandling, 200);
264    }         
265}
266
267/**
268 * traverses the DOM-structure of the HTML-site and determines the URL of this script. Based on
269 * this URL, it calculates the destination to which the traced interactions must be sent
270 */
271function determineDestination() {
272    var scriptElements = document.getElementsByTagName("script");
273    var i;
274    var index;
275   
276    for (i = 0; i < scriptElements.length; i++) {
277        if ((scriptElements[i].type === "text/javascript") && (scriptElements[i].src)) {
278            index = scriptElements[i].src.lastIndexOf("script/autoquest-htmlmonitor.js");
279            if (index > -1) {
280                autoquestDestination = scriptElements[i].src.substring(0, index - 1);
281                log("using destination " + autoquestDestination);
282            }
283        }
284    }
285}
286
287/**
288 * traverses the DOM-structure of the HTML-site and adds event handling attributes to each
289 * relevant node. Furthermore returns a JSON representation of the node including the children
290 *
291 * @param node       the node of the DOM structure that shall be adapted and whose children shall
292 *                   be traversed
293 * @param parentPath the path to the parent node of the provided node within the DOM-structure of
294 *                   the HTML-site
295 */
296function addEventHandlingAndGetJSONRepresentation(node, parentPath) {
297    var nodePath;
298    var i;
299    var jsonRepresentation = null;
300    var childRepresentation;
301    var childRepresentations = null;
302   
303    if (node.nodeType === Node.ELEMENT_NODE) {
304        jsonRepresentation = "{\"tagName\":\"" + getTagName(node) + "\",";
305       
306        if ((node.id) && (node.id !== "")) {
307            jsonRepresentation += "\"id\":\"" + node.id + "\"";
308        }
309        else {
310            jsonRepresentation += "\"index\":\"" + getNodeIndex(node) + "\"";
311        }
312       
313        addEventHandling(node, parentPath);
314       
315        if (node.childNodes.length > 0) {
316            nodePath = getNodePath(node, parentPath);
317           
318            for (i = 0; i < node.childNodes.length; i++) {
319                childRepresentation =
320                    addEventHandlingAndGetJSONRepresentation(node.childNodes[i], nodePath);
321               
322                if (childRepresentation) {
323                    if (!childRepresentations) {
324                        childRepresentations = childRepresentation;
325                    }
326                    else {
327                        childRepresentations += "," + childRepresentation;
328                    }
329                }
330            }
331
332            if (childRepresentations) {
333                jsonRepresentation += ",\"children\":[" + childRepresentations + "]";
334            }
335        }
336       
337        jsonRepresentation += "}";
338    }
339   
340    return jsonRepresentation;
341}
342
343/**
344 * adds event handling functionality to the provided node. Calls
345 * {@link #addEventHandlingWithJQuery(node,parentPath)} or
346 * {@link #addEventHandlingWithoutJQuery(node,parentPath)} depending on the fact if jQuery is
347 * available and must therefore be used, or not.
348 *
349 * @param node       the node of the DOM structure that shall be equipped with event handling
350 * @param parentPath the path to the parent node of the provided node within the DOM-structure of
351 *                   the HTML-site
352 */
353function addEventHandling(node, parentPath) {
354    if (typeof jQuery === 'undefined') {
355        addEventHandlingWithoutJQuery(node, parentPath);
356    }
357    else {
358        addEventHandlingWithJQuery(node, parentPath);
359    }
360}
361
362/**
363 * adds event handling functionality to the provided node using onxxx attributes
364 *
365 * @param node       the node of the DOM structure that shall be equipped with event handling
366 * @param parentPath the path to the parent node of the provided node within the DOM-structure of
367 *                   the HTML-site
368 */
369function addEventHandlingWithoutJQuery(node, parentPath) {
370    var nodePath = getNodePath(node, parentPath);
371    var tagName = getTagName(node);
372    var i;
373    var k;
374   
375    for (i = 0; i < autoquestActionConfig.length; i++) {
376        if (tagName === autoquestActionConfig[i].tag.toLowerCase()) {
377            for (k = 0; k < autoquestActionConfig[i].actions.length; k++) {
378                adaptEventHandlingAttribute(node, nodePath, autoquestActionConfig[i].actions[k]);
379            }
380        }
381    }
382}
383
384/**
385 * adds event handling functionality to the provided node using jQuery attributes. If the node
386 * already used onxxx attributes, these are extended instead of using jQuery.
387 *
388 * @param node       the node of the DOM structure that shall be equipped with event handling
389 * @param parentPath the path to the parent node of the provided node within the DOM-structure of
390 *                   the HTML-site
391 */
392function addEventHandlingWithJQuery(node, parentPath) {
393    var nodePath = getNodePath(node, parentPath);
394    var tagName = getTagName(node);
395    var action;
396    var parameters;
397    var i;
398    var k;
399   
400    for (i = 0; i < autoquestActionConfig.length; i++) {
401        if (tagName === autoquestActionConfig[i].tag.toLowerCase()) {
402            for (k = 0; k < autoquestActionConfig[i].actions.length; k++) {
403                action = autoquestActionConfig[i].actions[k];
404                if (jQuery(node).attr(action)) {
405                    // if there is an event handling attribute although jquery is present
406                    // edit this attribute accordingly
407                    adaptEventHandlingAttribute(node, nodePath, action);
408                }
409                else {
410                    parameters = { action : action, path : nodePath};
411                    if (jQuery(node).on) {
412                        jQuery(node).on(action.substring(2), parameters, handleJQueryEvent);
413                    }
414                    else {
415                        jQuery(node).bind(action.substring(2), parameters, handleJQueryEvent);
416                    }
417                }
418            }
419        }
420    }
421}
422
423/**
424 * adapts the event handling attributed provided by the action parameter so that it calls
425 * the {@link #handleEvent(node, action, path, even)} function in the case the event occurs.
426 * Either the method creates an appropriate onxxx attribute on the node if there is none, or it
427 * adds a call to the function as first thing called by the onxxx attribute.
428 *
429 * @param node     the node of the DOM structure that shall be equipped with event handling
430 * @param nodePath the path to node within the DOM-structure of the HTML-site
431 * @param action   the event for which event handling shall be enabled
432 */
433function adaptEventHandlingAttribute(node, nodePath, action) {
434    var value = "handleEvent(this, '" + action + "', '" + nodePath + "', event);";
435    var oldValue;
436   
437    if (!node.getAttribute(action)) {
438        node.setAttribute(action, value);
439    }
440    else {
441        oldValue = node.getAttribute(action);
442        if (oldValue.indexOf(value) < 0) {
443            node.setAttribute(action, value + ' ' + oldValue);
444        }
445    }
446}
447
448/**
449 * generates a path through the DOM-structure of the HTML-site depending on a node and the path
450 * to its parent node. The result is the parent path plus a path element for the provided node.
451 * The first part of the path element generated for the node is the tag name returned by
452 * {@link #getTagName(node)}. If the node has an id, it becomes the second part of the path
453 * element. If the node does not have an id, the method calculates the index of the node within
454 * all children of the same type within the parent node. This index becomes then the second part
455 * of the path element generated for the node.
456 *
457 * @param node       the node of the DOM structure for which the path shall be created
458 * @param parentPath the path to the parent node of the provided node
459 *
460 * @returns a path in the DOM-structure of the HTML-site including the parent path an a path
461 *          element for the provided node
462 */
463function getNodePath(node, parentPath) {
464    var nodePath = parentPath + "/" + getTagName(node);
465   
466    if ((node.id) && (node.id !== "")) {
467        nodePath += "(id=" + node.id + ")";
468    }
469    else {
470        nodePath += "[" + getNodeIndex(node) + "]";
471    }
472   
473    return nodePath;
474}
475
476/**
477 * TODO comment
478 */
479function handleJQueryEvent(event) {
480    handleEvent(this, event.data.action, event.data.path, event);
481}
482
483/**
484 * handles an event that happened on a node. This method is called by the event handling attributes
485 * of the nodes. These attributes are generated by the
486 * {@link #addEventHandlingAttributes(node,parentPath)} function. It creates a new Event object and
487 * adds it to the list of <code>autoquestRecordedEvents</code>. If this list achieves the maximum
488 * <code>autoquestPackageSize</code> the events in the list are sent to the server asynchronously
489 * through calling {@link #sendRequest()}.
490 *
491 * @param node      the node that fired the event
492 * @param eventName the name of the event, e.g. onscroll
493 * @param nodePath  the path to the node in the HTML DOM on which the event occurred
494 * @param event     the HTML event that occured
495 */
496function handleEvent(node, eventName, nodePath, event) {
497    var eventType;
498    var eventObj = null;
499    var tagName;
500
501    if (!autoquestDestination) {
502        // do nothing if we have no destination to send data to
503        return;
504    }
505   
506    log("handling event " + eventName + " on " + node);
507   
508    eventType = eventName.toLowerCase();
509
510    if (autoquestRecordedEvents.length > 0) {
511        eventObj = autoquestRecordedEvents[autoquestRecordedEvents.length - 1];
512
513        // check if an event showed up several times either for the same or for a parent GUI element
514        if ((eventObj.type === eventName) && (eventObj.nodePath.indexOf(nodePath) === 0)) {
515            // the event is of the same type.
516            if (eventObj.nodePath.length > nodePath.length) {
517                // the same event showed up for the parent GUI element. This must not be handled.
518                // So ignore it
519                log("discarding event " + eventName + " on " + node +
520                    " as it is already handled by a child");
521                return;
522            }
523            else if (eventName !== "onscroll") {
524                // we have the same event on the same element. If it is an onscroll, we should
525                // reuse it. But it is not an onscroll. So we ignore the existing event.
526                eventObj = null;
527            }
528        }
529        else {
530            // the event is not of an equal type as the previous one. So we will not reuse it
531            eventObj = null;
532        }
533    }
534   
535    if (!eventObj) {
536        // create a new event and add it to the list
537        eventObj = new Event(eventType, nodePath);
538        log("storing event " + eventName);
539        autoquestRecordedEvents.push(eventObj);
540    }
541
542    // now add further event parameters
543    if ((eventType === "onclick") || (eventType === "ondblclick")) {
544        eventObj.setClickCoordinates(getEventCoordinates(event));
545    }
546
547    tagName = getTagName(node);
548   
549    if ("input_password" !== tagName) {
550        if ((eventType === "onkeypress") ||
551            (eventType === "onkeydown") ||
552            (eventType === "onkeyup"))
553        {
554            eventObj.setKey(event.keyCode);
555        }
556        else if (eventType === "onchange") {
557            if ((tagName.indexOf("input") === 0) ||
558                (tagName === "textarea") ||
559                (tagName === "keygen"))
560            {
561                eventObj.setSelectedValue(node.value);
562            }
563            else if (tagName === "select") {
564                eventObj.setSelectedValue(node.options.item(node.options.selectedIndex));
565            }
566        }
567    }
568   
569    if (eventType === "onscroll") {
570        eventObj.setScrollPosition(getScrollCoordinates(node));
571    }
572
573    if (autoquestRecordedEvents.length >= autoquestPackageSize) {
574        log("initiating sending events");
575        setTimeout(sendRequest, 100);
576    }
577    else if ((eventType === "onunload") || (eventType === "onbeforeunload")) {
578        log("initiating sending events");
579        sendRequest();
580    }
581
582}
583
584/**
585 * determines a tag name of a node. If the node is an input element, the tag name includes the
586 * type of element. Otherwise the method simply returns the tag name.
587 *
588 * @param node the node for which the name must be determined
589 *
590 * @return the name as described
591 */
592function getTagName(node) {
593    var tagName = null;
594   
595    if (node.tagName) {
596        tagName = node.tagName.toLowerCase();
597        if ("input" === tagName) {
598            if (node.type && (node.type !== "")) {
599                tagName += "_" + node.type;
600            }
601            else {
602                tagName += "_text";
603            }
604        }
605    }
606
607    return tagName;
608}
609   
610/**
611 * determines the index of a node considering all nodes of the parent having the same name. If the
612 * node has no parent, the method returns index 0.
613 *
614 * @param node the node for which the index must be determined
615 *
616 * @return the index as described
617 */
618function getNodeIndex(node) {
619    var i;
620    var index = 0;
621   
622    if (node.parentNode) {
623        for (i = 0; i < node.parentNode.childNodes.length; i++) {
624            if (node.parentNode.childNodes[i].tagName === node.tagName) {
625                index++;
626                // if === also returns true if the nodes are not identical but only equal,
627                // this may fail.
628                if (node.parentNode.childNodes[i] === node) {
629                    break;
630                }
631            }
632        }
633       
634    }
635
636    return index;
637}
638   
639/**
640 * sends the collected data to the server, named in the destination-variable. For this it generates
641 * a JSON formatted message and uses Ajax and the <code>autoquestDestination</code> to send it to
642 * the server
643 */
644function sendRequest() {
645    var eventList = autoquestRecordedEvents;
646    var message;
647    var clientId;
648    var i = 0;
649    var request;
650   
651    if (eventList.length > 1) {
652        log("creating message");
653       
654        // put the last event into the new list to allow for checks for reoccurence of the same
655        // event
656        autoquestRecordedEvents = [ eventList.pop() ];
657       
658        message = "{\"message\":{\"clientInfos\":{";
659       
660        log("reading client id");
661        clientId = getClientId();
662        if ((clientId) && (clientId !== "")) {
663            message += "\"clientId\":\"" + clientId + "\",";
664        }
665       
666        log("adding other infos");
667        message += "\"userAgent\":\"" + navigator.userAgent + "\",";
668        message += "\"title\":\"" + document.title + "\",";
669        message += "\"url\":\"" + document.URL + "\"},";
670       
671        message += "\"guiModel\":" + autoquestGUIModel + ",";
672       
673        message += "\"events\":[";
674       
675        for (i = 0; i < eventList.length; i++) {
676            if (i > 0) {
677                message += ",";
678            }
679            message += eventList[i].toJSON();
680        }
681       
682        message += "]}}";
683       
684        request = null;
685       
686        // Mozilla
687        if (window.XMLHttpRequest) {
688            request = new XMLHttpRequest();
689        }
690        // IE
691        else if (window.ActiveXObject) {
692            request = new ActiveXObject("Microsoft.XMLHTTP");
693        }
694       
695        request.open("POST", autoquestDestination, false);
696        request.setRequestHeader("Content-Type", "application/json");
697
698        log("sending " + message);
699        request.send(message);
700    }
701}
702
703/**
704 * determines the scroll coordinates of the scrolled element
705 *
706 * @param node the element that was scrolled
707 *
708 * @returns the coordinates of the scrolling as an array with x and y coordinate
709 */
710function getScrollCoordinates(node) {
711    if (node.scrollLeft || node.scrollTop) {
712        return [node.scrollLeft, node.scrollTop];
713    }
714    else if ((node === window) && window.pageYOffset) {
715        return [window.pageXOffset, window.pageYOffset];
716    }
717    else if ((node == document) || (node == document.body) || (node == document.documentElement)) {
718        if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
719            return [document.body.scrollLeft, document.body.scrollTop];
720        }
721        else if (document.documentElement &&
722                 (document.documentElement.scrollLeft || document.documentElement.scrollTop))
723        {
724            return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
725        }
726    }
727
728    return [-1, -1];
729}
730
731/**
732 * determines the coordinates of an onclick or ondblclick event. If the coordinates to not come
733 * with the provided event, they are determined based on the surrounding object
734 *
735 * @param event the event to extract the coordinates of
736 *
737 * @returns the coordinates of the event as an array with x and y coordinate
738 */
739function getEventCoordinates(event) {
740    if (event.layerX) {
741        return [event.layerX, event.layerY];
742    }
743    else if (event.offsetX) {
744        return [event.offsetX, event.offsetY];
745    }
746
747    var obj = event.target || event.srcElement;
748    var objOffset = getPageOffset(obj);
749
750    return [(event.clientX - objOffset[0]), (event.clientY - objOffset[1])];
751}
752
753/**
754 * determines the page offset of an object using the parent objects offset
755 */
756function getPageOffset(object) {
757    var top = 0;
758    var left = 0;
759    var obj = object;
760
761    while (obj.offsetParent) {
762        left += obj.offsetLeft;
763        top += obj.offsetTop;
764        obj = obj.offsetParent;
765    }
766
767    return [left, top];
768}
769
770/**
771 * generates a client id based on several information retrieved from the environment. The client
772 * id is not always unique
773 *
774 * @returns the client id
775 */
776function getClientId() {
777    var clientIdStr;
778    var clientId;
779    var i = 0;
780   
781    if (!autoquestClientId) {
782        // create something like a more or less unique checksum.
783        clientIdStr =
784            navigator.appCodeName + navigator.appName + navigator.appVersion +
785            navigator.cookieEnabled + navigator.language + navigator.platform +
786            navigator.userAgent + navigator.javaEnabled() + window.location.protocol +
787            window.location.host + new Date().getTimezoneOffset();
788
789        clientId = clientIdStr.length;
790
791        for (i = 0; i < clientIdStr.length; i++) {
792            clientId += clientIdStr.charCodeAt(i);
793        }
794       
795        autoquestClientId = clientId;
796    }
797
798    return autoquestClientId;
799}
800
801/**
802 * performs a simple logging by adding a specific div to the HTML
803 *
804 * @param text the text to be logged
805 */
806function log(text) {
807    if (autoquestDoLog) {
808        var loggingInfo = document.getElementById("loggingInfoParagraph");
809       
810        if (!loggingInfo) {
811            var div = document.createElement("div");
812            div.setAttribute("style", "z-index:1000000; background-color:#afa; " +
813                             "border:1px black solid; position:absolute; top:10px; right:10px; " +
814                             "width:350px; height:auto; font-size:8pt; font-family:Helvetica");
815           
816            loggingInfo = document.createElement("div");
817            loggingInfo.id = "loggingInfoParagraph";
818            div.appendChild(loggingInfo);
819           
820            var body = document.getElementsByTagName("body")[0];
821            if (!body) {
822                alert("could not enable logging");
823            }
824            else {
825                body.appendChild(div);
826            }
827        }
828       
829        loggingInfo.appendChild(document.createTextNode(text));
830        loggingInfo.appendChild(document.createElement("br"));
831    }
832}
833
834/**
835 * this class represents a single event
836 *
837 * @param type     the type of the event
838 * @param nodePath the path through the HTML DOM to the event target
839 */
840function Event(type, nodePath) {
841    this.type = type;
842    this.nodePath = nodePath;
843    this.time = new Date().getTime();
844   
845    this.setClickCoordinates = function(coordinates) {
846          this.clickCoordinates = coordinates;
847    };
848   
849    this.setKey = function(key) {
850          this.key = key;
851    };
852   
853    this.setSelectedValue = function(value) {
854        this.selectedValue = encodeText(value);
855    };
856 
857    this.setScrollPosition = function(scrollPosition) {
858        this.scrollPosition = scrollPosition;
859    };
860   
861    this.toJSON = function() {
862        var eventInJSON =
863            "{\"path\":\"" + this.nodePath + "\",\"time\":" + this.time + ",\"eventType\":\"" +
864            this.type + "\"";
865
866        if ((this.clickCoordinates) && (!isNaN(this.clickCoordinates[0]))) {
867            eventInJSON += ",\"coordinates\":[" + this.clickCoordinates + "]";
868        }
869
870        if (this.key) {
871            eventInJSON += ",\"key\":" + this.key;
872        }
873       
874        if (this.selectedValue) {
875            eventInJSON += ",\"selectedValue\":\"" + this.selectedValue + "\"";
876        }
877       
878        if ("onscroll" === this.type) {
879            eventInJSON += ",\"scrollPosition\":[" + this.scrollPosition + "]";
880        }
881
882        eventInJSON += "}";
883       
884        return eventInJSON;
885    };
886   
887    function encodeText(text) {
888        return text.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"");
889    };
890}
Note: See TracBrowser for help on using the repository browser.