Source: tracklib.js

  1. /*! evtrack -- Lib module */
  2. /**
  3. * Auxiliary functions to track the user activity.
  4. * Library borrowed from {@link https://github.com/luileito/evtrack}
  5. * @author Luis Leiva
  6. * @version 0.2
  7. * @license Dual licensed under the MIT and GPL licenses.
  8. */
  9. var TrackLib = window.TrackLib || {};
  10. /**
  11. * XPath functions.
  12. * Not documented yet.
  13. * Code adapted from window.js at {@link http://code.google.com/p/xpathchecker/}
  14. * @author Brian Slesinsky ({@link http://slesinsky.org})
  15. */
  16. TrackLib.XPath = {
  17. queryXPath: function(document, xpath) {
  18. var iterator;
  19. if (typeof document.evaluate === 'function') {
  20. iterator = document.evaluate(xpath, document.documentElement, null, XPathResult.ANY_TYPE, null);
  21. } else {
  22. try {
  23. // IE5 and later has implemented that [0] should be the first node,
  24. // but according to the W3C standard it should have been [1]!
  25. document.setProperty("SelectionLanguage", "XPath");
  26. iterator = document.selectNodes(xpath);
  27. } catch(err) {
  28. iterator = false;
  29. }
  30. }
  31. return iterator;
  32. },
  33. getXPathNodes: function(document, xpath) {
  34. var iterator = this.queryXPath(document, xpath);
  35. var result = [];
  36. var item = iterator.iterateNext();
  37. while (item) {
  38. result.push(item);
  39. item = iterator.iterateNext();
  40. }
  41. return result;
  42. },
  43. getXPath: function(targetNode, absolute) {
  44. var lowerCase = (targetNode.ownerDocument instanceof HTMLDocument)
  45. , xNodePath = this.getNodePath(targetNode, absolute)
  46. , nodeNames = []
  47. ;
  48. for (var i in xNodePath) {
  49. var node = xNodePath[i]
  50. , nIdx
  51. ;
  52. if (node.nodeType == 1) {
  53. if (i == 0 && !absolute && node.hasAttribute("id")) {
  54. nodeNames.push("/*[@id='" + node.getAttribute("id") + "']");
  55. } else {
  56. var tagName = node.tagName;
  57. if (lowerCase) {
  58. tagName = tagName.toLowerCase();
  59. }
  60. nIdx = this.getNodeIndex(node);
  61. if (nIdx != null) {
  62. nodeNames.push(tagName + "[" + nIdx + "]");
  63. } else {
  64. nodeNames.push(tagName);
  65. }
  66. }
  67. } else if (node.nodeType == 3) {
  68. nIdx = this.getTextNodeIndex(node);
  69. if (nIdx != null) {
  70. nodeNames.push("text()[" + nIdx + "]");
  71. } else {
  72. nodeNames.push("text()");
  73. }
  74. }
  75. }
  76. return "/" + nodeNames.join("/");
  77. },
  78. getNodeIndex: function(node) {
  79. if (node.nodeType != 1 || node.parentNode == null) return null;
  80. var list = this.getChildNodesWithTagName(node.parentNode, node.tagName);
  81. if (list.length == 1 && list[0] == node) return null;
  82. for (var i = 0; i < list.length; i++) {
  83. if (list[i] == node) return i + 1;
  84. }
  85. throw new Error("couldn't find node in parent's list: " + node.tagName);
  86. },
  87. getTextNodeIndex: function(node) {
  88. var list = this.getChildTextNodes(node.parentNode)
  89. if (list.length == 1 && list[0] == node) return null;
  90. for (var i = 0; i < list.length; i++) {
  91. if (list[i] == node) return i + 1;
  92. }
  93. throw new Error("couldn't find node in parent's list: " + node.tagName);
  94. },
  95. getChildNodesWithTagName: function(parent, tagName) {
  96. var result = [], child = parent.firstChild;
  97. while (child != null) {
  98. if (child.tagName && child.tagName == tagName) {
  99. result.push(child);
  100. }
  101. child = child.nextSibling;
  102. }
  103. return result;
  104. },
  105. getChildTextNodes: function(parent) {
  106. var result = [], child = parent.firstChild;
  107. while (child != null) {
  108. if (child.nodeType == 3) {
  109. result.push(child);
  110. }
  111. child = child.nextSibling;
  112. }
  113. return result;
  114. },
  115. getNodePath: function(node, absolute) {
  116. var result = [];
  117. while (node.nodeType == 1 || node.nodeType == 3) {
  118. result.unshift(node);
  119. if (node.nodeType == 1 && node.hasAttribute("id") && !absolute) return result;
  120. node = node.parentNode;
  121. }
  122. return result;
  123. },
  124. getNodeValues: function(resultList) {
  125. var result = [];
  126. for (var i in resultList) {
  127. result.push(resultList[i].nodeValue);
  128. }
  129. return result;
  130. }
  131. };
  132. /**
  133. * Ajax handling object.
  134. */
  135. TrackLib.XHR = {
  136. /**
  137. * Creates an XML/HTTP request to provide async communication with the server.
  138. * @return {object} XHR object
  139. * @autor Peter-Paul Koch ({@link http://quirksMode.org})
  140. */
  141. createXMLHTTPObject: function() {
  142. var xmlhttp = false;
  143. // Current AJAX flavors
  144. var factories = [
  145. function(){ return new XMLHttpRequest(); },
  146. function(){ return new ActiveXObject("Msxml2.XMLHTTP"); },
  147. function(){ return new ActiveXObject("Msxml3.XMLHTTP"); },
  148. function(){ return new ActiveXObject("Microsoft.XMLHTTP"); }
  149. ];
  150. // Check AJAX flavor
  151. for (var i = 0; i < factories.length; ++i) {
  152. try {
  153. xmlhttp = factories[i]();
  154. } catch(e) { continue; }
  155. break;
  156. }
  157. return xmlhttp;
  158. },
  159. /**
  160. * Makes an asynchronous XMLHTTP request (XHR) via GET or POST.
  161. * Inspired by Peter-Paul Koch's old XMLHttpRequest wrapper function.
  162. * Note: CORS on IE will work only for version 8 or higher.
  163. * @param {object} setup Request properties
  164. * @config {string} url Request URL
  165. * @config {boolean} [async] Asynchronous request (or not)
  166. * @config {function} [callback] Response function
  167. * @config {string} [postdata] POST vars in the form "var1=name&var2=name..."
  168. * @config {object} [xmlhttp] A previous XMLHTTP object can be reused
  169. */
  170. sendAjaxRequest: function(setup) {
  171. // Create XHR object or reuse it
  172. var request = setup.xmlhttp ? setup.xmlhttp : this.createXMLHTTPObject();
  173. var cors = !TrackLib.Util.sameDomain(window.location.href, setup.url);
  174. // CORS does work with XMLHttpRequest on modern browsers, except IE
  175. if (cors && window.XDomainRequest) {
  176. request = new XDomainRequest();
  177. }
  178. if (!request) return false;
  179. var method = setup.postdata ? "POST" : "GET";
  180. var asynchronous = setup.hasOwnProperty('async') ? setup.async : true;
  181. // Start request
  182. request.open(method, setup.url, asynchronous);
  183. var iecors = window.XDomainRequest && (request instanceof XDomainRequest);
  184. // Post requests must set the correct content type (not allowed under CORS + IE, though)
  185. if (setup.postdata && !iecors) {
  186. request.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
  187. }
  188. // Add load listener
  189. if (iecors) {
  190. request.onload = function(){
  191. if (typeof setup.callback === 'function') setup.callback(request.responseText);
  192. };
  193. } else {
  194. // Check for the 'complete' request state
  195. request.onreadystatechange = function(){
  196. if (request.readyState == 4 && typeof setup.callback === 'function') {
  197. setup.callback(request.responseText);
  198. }
  199. };
  200. }
  201. request.send(setup.postdata);
  202. }
  203. };
  204. /**
  205. * Event handling object.
  206. */
  207. TrackLib.Events = {
  208. /**
  209. * Adds event listeners unobtrusively.
  210. * @author John Resig ({@link http://ejohn.org})
  211. * @param {object} obj Object to add listener(s) to.
  212. * @param {string} type Event type.
  213. * @param {function} fn Function to execute.
  214. */
  215. add: function(obj, type, fn) {
  216. if (!obj) return false;
  217. if (obj.addEventListener) { // W3C standard
  218. obj.addEventListener(type, fn, false);
  219. } else if (obj.attachEvent) { // IE versions
  220. obj.attachEvent("on"+type, fn);
  221. } else { // Really old browser
  222. obj[type+fn] = function(){ fn(window.event); };
  223. }
  224. },
  225. addMulti: function(obj, events, fn) {
  226. if (typeof events === "string") events = events.split(" ");
  227. for (var e = 0; e < events.length; e++) {
  228. this.add(obj, events[e], fn);
  229. }
  230. },
  231. /**
  232. * Removes event listeners unobtrusively.
  233. * @author John Resig ({@link http://ejohn.org})
  234. * @param {object} obj Object to remove listener(s) from
  235. * @param {string} type Event type
  236. * @param {function} fn Function to remove from event
  237. */
  238. remove: function(obj, type, fn) {
  239. if (!obj) return false;
  240. if (obj.removeEventListener) { // W3C standard
  241. obj.removeEventListener(type, fn, false);
  242. } else if (obj.detachEvent) { // IE versions
  243. obj.detachEvent("on"+type, fn);
  244. } else { // Really old browser
  245. obj[type+fn] = null;
  246. }
  247. },
  248. /**
  249. * Fixes event handling inconsistencies between browsers.
  250. * @param {object} e Event
  251. * @return {object} Fixed event
  252. */
  253. fix: function(e) {
  254. e = e || window.event;
  255. // Fix target property, if necessary (IE 6/7/8 & Safari 2)
  256. if (!e.target) e.target = e.srcElement || document;
  257. // Target should not be a text node (Safari bug)
  258. if (e.target.nodeType == 3) e.target = e.target.parentNode;
  259. // For mouse/key events; add metaKey if it's not there (IE 6/7/8)
  260. if (typeof e.metaKey === 'undefined') e.metaKey = e.ctrlKey;
  261. // Support multitouch events (index 0 is consistent with mobile devices)
  262. e.id = e.identifier || 0;
  263. return e;
  264. },
  265. /**
  266. * Executes callback on DOM load.
  267. * @param {function} callback
  268. */
  269. domReady: function(callback) {
  270. if (arguments.callee.done) return;
  271. arguments.callee.done = true;
  272. if (document.addEventListener) {
  273. // W3C browsers
  274. document.addEventListener('DOMContentLoaded', callback, false);
  275. }
  276. else if (document.attachEvent) {
  277. // Internet Explorer ¬¬
  278. try {
  279. document.write("<scr"+"ipt id=__ie_onload defer=true src=//:><\/scr"+"ipt>");
  280. var script = document.getElementById("__ie_onload");
  281. script.onreadystatechange = function() {
  282. if (this.readyState === 'complete') { callback(); }
  283. };
  284. } catch(err) {}
  285. }
  286. else {
  287. // Really old browsers
  288. TrackLib.Events.add(window, 'load', callback);
  289. }
  290. }
  291. };
  292. /**
  293. * Dimension handling object.
  294. */
  295. TrackLib.Dimension = {
  296. /**
  297. * Gets the browser's window size (aka 'the viewport').
  298. * @return {object} window dimmensions
  299. * @config {integer} width
  300. * @config {integer} height
  301. */
  302. getWindowSize: function() {
  303. var d = document;
  304. var w = (window.innerWidth) ? window.innerWidth
  305. : (d.documentElement && d.documentElement.clientWidth) ? d.documentElement.clientWidth
  306. : (d.body && d.body.clientWidth) ? d.body.clientWidth
  307. : 0;
  308. var h = (window.innerHeight) ? window.innerHeight
  309. : (d.documentElement && d.documentElement.clientHeight) ? d.documentElement.clientHeight
  310. : (d.body && d.body.clientHeight) ? d.body.clientHeight
  311. : 0;
  312. return { width: w, height: h };
  313. },
  314. /**
  315. * Gets the document's size.
  316. * @return {object} document dimensions
  317. * @config {integer} width
  318. * @config {integer} height
  319. */
  320. getDocumentSize: function() {
  321. var d = document;
  322. var w = (window.innerWidth && window.scrollMaxX) ? window.innerWidth + window.scrollMaxX
  323. : (d.body && d.body.scrollWidth > d.body.offsetWidth) ? d.body.scrollWidth
  324. : (d.body && d.body.offsetWidth) ? d.body.offsetWidth
  325. : 0;
  326. var h = (window.innerHeight && window.scrollMaxY) ? window.innerHeight + window.scrollMaxY
  327. : (d.body && d.body.scrollHeight > d.body.offsetHeight) ? d.body.scrollHeight
  328. : (d.body && d.body.offsetHeight) ? d.body.offsetHeight
  329. : 0;
  330. return { width: w, height: h };
  331. },
  332. /**
  333. * Gets the max value from both window (viewport's size) and document's size.
  334. * @return {object} viewport dimensions
  335. * @config {integer} width
  336. * @config {integer} height
  337. */
  338. getPageSize: function() {
  339. var win = this.getWindowSize(),
  340. doc = this.getDocumentSize();
  341. // Find max values from this group
  342. var w = (doc.width < win.width) ? win.width : doc.width;
  343. var h = (doc.height < win.height) ? win.height : doc.height;
  344. return { width: w, height: h };
  345. },
  346. getElementSize: function(node) {
  347. return { width: node.offsetWidth, height: node.offsetHeight };
  348. },
  349. getElementPosition: function(node) {
  350. var curleft = 0, curtop = 0;
  351. if (node && node.offsetParent) {
  352. do {
  353. curleft += node.offsetLeft;
  354. curtop += node.offsetTop;
  355. } while (node = node.offsetParent);
  356. }
  357. return { left: curleft, top: curtop };
  358. }
  359. };
  360. /**
  361. * Some utilies.
  362. */
  363. TrackLib.Util = {
  364. /**
  365. * Tests whether a set of URLs come from the same domain.
  366. * @return {boolean}
  367. */
  368. sameDomain: function() {
  369. var prevDomain, sameDomain = true;
  370. for (var i = 0, l = arguments.length; i < l; ++i) {
  371. if (i > 0) {
  372. sameDomain = (this.getDomain(prevDomain) == this.getDomain(arguments[i]));
  373. }
  374. prevDomain = arguments[i];
  375. }
  376. return sameDomain;
  377. },
  378. /**
  379. * Gets the domain of a given URL.
  380. * @return {string}
  381. */
  382. getDomain: function(url) {
  383. var d, link = document.createElement("a");
  384. link.href = url;
  385. d = link.hostname;
  386. link = null; // free
  387. return d;
  388. },
  389. /**
  390. * Serializes the attributes of a DOM node.
  391. * @param {object} elem DOM node
  392. * @return {string} JSON representation of the node attributes
  393. */
  394. serializeAttrs: function(elem) {
  395. var obj = {};
  396. if (elem && elem.attributes) {
  397. obj[elem.nodeName] = {};
  398. for (var i = 0, t = elem.attributes.length; i < t; i++) {
  399. var attrib = elem.attributes[i];
  400. if (attrib.specified) {
  401. obj[elem.nodeName][attrib.name] = attrib.value;
  402. }
  403. }
  404. }
  405. return JSON.stringify(obj);
  406. }
  407. };