// The MIT License (MIT) // // Copyright (c) 2013 Revolving Cow, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // Check to see if a string starts with the given search criteria. // @param {String} search // @return {Boolean} a value indicating whether the string starts with the search criteria String.prototype.startsWith = function (search) { return this.indexOf(search) == 0; }; // Check to see if a string ends with the given search criteria. // @param {String} search // @return {Boolean} a value indicating whether the string ends with the search criteria String.prototype.endsWith = function (search) { return this.lastIndexOf(search) == this.length - search.length; }; (function ($) { // Declared outside of scope to maintain an accurate count. var uniqueId = 0; // Default settings which may be extended upon. var settings = { attributes: [], assignTo: ["a", "input[type='submit']"], captureOnce: false, client: null, id: "id", exclude: ".analytics-exclude", url: null }; // Determines if there is an exclusion in the path. // @param {Object} element // @return {Boolean} a value indicating whether the object is excluded function isExcluded(element) { var excluded = $(element).is(settings.exclude); var parent = $(element).parent(); if (parent && parent.prop("tagName") !== undefined) { excluded = excluded || parent.is(settings.excluded); if (!excluded) { excluded = excluded || isExcluded(parent); } } return excluded; }; // Walk the tree of a given node. // @param {Object} element // @return {Array} path function walkTree(element) { var tree = []; var tagName = $(element).prop("tagName"); if (tagName != undefined) { var parent = $(element).parent(); if (parent && parent.prop("tagName") !== undefined) { $.each(walkTree($(element).parent()), function (i, node) { tree.push(node); }); } if (tagName == "HTML" || tagName == "BODY") { tree.push(tagName); } else { var tagId = $(element).analyticsUniqueId().attr("id"); tree.push(tagName + '[id="' + tagId + '"]'); } } return tree; }; // Identify the path to the node. // @param {Object} node function identifyPath(node) { // Assign identification to all relevant elements. walkTree(node); }; // Initiate a trace on click. // @param {Object} e function initiateTrace(e) { // Locally scope this variable. $this = $(this); if (settings.url && !$this.is(".analytics-captured") && !isExcluded($this)) { // Walk the tree. var tree = walkTree($this); // Make sure the tree does not include an excluded section. if (tree && tree.length > 0) { // Join all the nodes of the tree. tree = tree.join(' '); // We prevent the default action to allow the background call to succeed. var preventedHref = null; if ($this.attr("href")) { e.preventDefault(); preventedHref = $this.attr("href"); } // Initialize the data to be collected. var data = {}; // Attach the object identifier. if (settings.id) { data[settings.id] = tree; } else { data["id"] = tree; } // Attach the client identifier if found. if (settings.client) { data["client"] = settings.client } // Assign any "data-analytics-" attributes. var dataAttributes = $this.data(); for (var attribute in dataAttributes) { if (attribute.startsWith("analytics")) { var cleanName = attribute.replace(/analytics/g, '').toLowerCase(); data[cleanName] = dataAttributes[attribute]; } } // Assign the custom attributes requested to be collected. $.each($(settings.attributes), function (i, attribute) { data[attribute] = $this.attr(attribute); }); // Send the analytics. $.ajax({ type: "POST", url: settings.url, contentType: "application/x-www-form-urlencoded", data: data }) .always(function () { if (settings.captureOnce) { $this.addClass("analytics-captured"); } if (preventedHref) { window.location = preventedHref; } }); } } }; // Provide a unique identifier to an element if one has not already been assigned. // @return {Object} modified jQuery objects $.fn.analyticsUniqueId = function () { if (this.length == 0) { return; } return this.each(function () { if (!$(this).attr("id")) { $(this).attr("id", "analytics-id-" + ++uniqueId); } }); }; // Plug-in function providing easy access to analytics. // @param {Object} options // @returns {Object} modified jQuery objects $.fn.analytics = function (options) { if ($(this).length == 0) { return; } // Configure the default settings. settings = $.extend({}, settings, options); // Declare the selector to be used. var selector = settings.assignTo.join(","); return this.each(function () { // Iterate through all elements given on initiation. $(this).find(selector).andSelf().filter(selector) .each(function () { identifyPath($(this)); $(this).on("click", initiateTrace); }); }) .on("DOMNodeInserted", function (e) { // This will capture any dynamically generated content. $(e.target).find(selector).andSelf().filter(selector) .each(function () { identifyPath($(this)); $(this).on("click", initiateTrace); }); }); }; })(jQuery);