jQuery plug-in to provide custom analytics. For those of us who can not use Google Analytics at work or just want to dork with something else.

jquery-analytics.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2013 Bryan Allred <bryan.allred@gmail.com>
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. // Check to see if a string starts with the given search criteria.
  23. // @param {String} search
  24. // @return {Boolean} a value indicating whether the string starts with the search criteria
  25. String.prototype.startsWith = function (search) {
  26. return this.indexOf(search) == 0;
  27. };
  28. // Check to see if a string ends with the given search criteria.
  29. // @param {String} search
  30. // @return {Boolean} a value indicating whether the string ends with the search criteria
  31. String.prototype.endsWith = function (search) {
  32. return this.lastIndexOf(search) == this.length - search.length;
  33. };
  34. (function ($) {
  35. // Declared outside of scope to maintain an accurate count.
  36. var uniqueId = 0;
  37. // Default settings which may be extended upon.
  38. var settings = {
  39. attributes: [],
  40. assignTo: ["a", "input[type='submit']"],
  41. captureOnce: false,
  42. client: null,
  43. id: "id",
  44. exclude: ".analytics-exclude",
  45. url: null
  46. };
  47. // Determines if there is an exclusion in the path.
  48. // @param {Object} element
  49. // @return {Boolean} a value indicating whether the object is excluded
  50. function isExcluded(element) {
  51. var excluded = $(element).is(settings.exclude);
  52. var parent = $(element).parent();
  53. if (parent && parent.prop("tagName") !== undefined) {
  54. excluded = excluded || parent.is(settings.excluded);
  55. if (!excluded) {
  56. excluded = excluded || isExcluded(parent);
  57. }
  58. }
  59. return excluded;
  60. };
  61. // Walk the tree of a given node.
  62. // @param {Object} element
  63. // @return {Array} path
  64. function walkTree(element) {
  65. var tree = [];
  66. var tagName = $(element).prop("tagName");
  67. if (tagName != undefined) {
  68. var parent = $(element).parent();
  69. if (parent && parent.prop("tagName") !== undefined) {
  70. $.each(walkTree($(element).parent()), function (i, node) {
  71. tree.push(node);
  72. });
  73. }
  74. if (tagName == "HTML" || tagName == "BODY") {
  75. tree.push(tagName);
  76. }
  77. else {
  78. var tagId = $(element).analyticsUniqueId().attr("id");
  79. tree.push(tagName + '[id="' + tagId + '"]');
  80. }
  81. }
  82. return tree;
  83. };
  84. // Identify the path to the node.
  85. // @param {Object} node
  86. function identifyPath(node) {
  87. // Assign identification to all relevant elements.
  88. walkTree(node);
  89. };
  90. // Initiate a trace on click.
  91. // @param {Object} e
  92. function initiateTrace(e) {
  93. // Locally scope this variable.
  94. $this = $(this);
  95. if (settings.url && !$this.is(".analytics-captured") && !isExcluded($this)) {
  96. // Walk the tree.
  97. var tree = walkTree($this);
  98. // Make sure the tree does not include an excluded section.
  99. if (tree && tree.length > 0) {
  100. // Join all the nodes of the tree.
  101. tree = tree.join(' ');
  102. // We prevent the default action to allow the background call to succeed.
  103. var preventedHref = null;
  104. if ($this.attr("href")) {
  105. e.preventDefault();
  106. preventedHref = $this.attr("href");
  107. }
  108. // Initialize the data to be collected.
  109. var data = {};
  110. // Attach the object identifier.
  111. if (settings.id) {
  112. data[settings.id] = tree;
  113. }
  114. else {
  115. data["id"] = tree;
  116. }
  117. // Attach the client identifier if found.
  118. if (settings.client) {
  119. data["client"] = settings.client
  120. }
  121. // Assign any "data-analytics-" attributes.
  122. var dataAttributes = $this.data();
  123. for (var attribute in dataAttributes) {
  124. if (attribute.startsWith("analytics")) {
  125. var cleanName = attribute.replace(/analytics/g, '').toLowerCase();
  126. data[cleanName] = dataAttributes[attribute];
  127. }
  128. }
  129. // Assign the custom attributes requested to be collected.
  130. $.each($(settings.attributes), function (i, attribute) {
  131. data[attribute] = $this.attr(attribute);
  132. });
  133. // Send the analytics.
  134. $.ajax({
  135. type: "POST",
  136. url: settings.url,
  137. contentType: "application/x-www-form-urlencoded",
  138. data: data
  139. })
  140. .always(function () {
  141. if (settings.captureOnce) {
  142. $this.addClass("analytics-captured");
  143. }
  144. if (preventedHref) {
  145. window.location = preventedHref;
  146. }
  147. });
  148. }
  149. }
  150. };
  151. // Provide a unique identifier to an element if one has not already been assigned.
  152. // @return {Object} modified jQuery objects
  153. $.fn.analyticsUniqueId = function () {
  154. if (this.length == 0) {
  155. return;
  156. }
  157. return this.each(function () {
  158. if (!$(this).attr("id")) {
  159. $(this).attr("id", "analytics-id-" + ++uniqueId);
  160. }
  161. });
  162. };
  163. // Plug-in function providing easy access to analytics.
  164. // @param {Object} options
  165. // @returns {Object} modified jQuery objects
  166. $.fn.analytics = function (options) {
  167. if ($(this).length == 0) {
  168. return;
  169. }
  170. // Configure the default settings.
  171. settings = $.extend({}, settings, options);
  172. // Declare the selector to be used.
  173. var selector = settings.assignTo.join(",");
  174. return this.each(function () {
  175. // Iterate through all elements given on initiation.
  176. $(this).find(selector).andSelf().filter(selector)
  177. .each(function () {
  178. identifyPath($(this));
  179. $(this).on("click", initiateTrace);
  180. });
  181. })
  182. .on("DOMNodeInserted", function (e) {
  183. // This will capture any dynamically generated content.
  184. $(e.target).find(selector).andSelf().filter(selector)
  185. .each(function () {
  186. identifyPath($(this));
  187. $(this).on("click", initiateTrace);
  188. });
  189. });
  190. };
  191. })(jQuery);