/*!
* jQuery Google Analytics Plugin v2.0.7
* https://github.com/JimBobSquarePants/jQuery-Google-Analytics-Plugin
* Copyright 2013, James South
* Released under the Apache 2.0 license
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
/*It is recommended that this file is minified before serving.*/
/*global jQuery, console */
/*jshint forin:true, noarg:true, noempty:true, eqnull:true, eqeqeq:true, bitwise:false, strict:true, curly:true, browser:true, maxerr:50 */
(function ($, window) {
"use strict";
// Custom ga selector.
$.extend($.expr[":"], {
ga: function (a) {
var attr = a.attributes,
len = attr.length;
while (len--) {
if (attr[len].name.indexOf("data-ga-") !== -1) {
return true;
}
}
return false;
}
});
$.googleAnalyticsApi = {
///
/// A representation of available methods provided by Google analytics.
///
trackEvent: {
///
/// The trackEvent object defines all the necessary parameters
/// to send event tracking analytics data to Google.
/// https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
///
event: {
value: "_trackEvent",
validation: "isString",
type: "string"
},
category: {
value: null,
validation: "isString",
type: "string"
},
action: {
value: null,
validation: "isString",
type: "string"
},
label: {
value: null,
validation: "optString",
type: "string"
},
value: {
value: null,
validation: "optInt",
type: "integer"
},
nonInteraction: {
value: null,
validation: "optBool",
type: "boolean"
}
},
trackPageview: {
///
/// The trackEvent object defines all the necessary parameters
/// to send page tracking analytics data to Google.
/// https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._trackPageview
///
event: {
value: "_trackPageview",
validation: "isString",
type: "string"
},
url: {
value: null,
validation: "optString",
type: "string"
}
}
};
var validation = {
///
/// A series of methods designed to test data-attribute values.
///
isString: function (obj) {
///
/// Checks to see if a given object is a valid non-empty string.
///
///
/// The object to check against.
///
/// True if the string is valid, otherwise false.
var empty = true;
// Don't test the type here just test for emptiness.
if (obj) {
empty = /^\s*$/.test(obj);
}
// If empty === true then something is wrong and we should return false.
return !(empty);
},
optString: function (obj) {
///
/// Checks to see if a given object is a valid non-empty string.
/// Since the object is optional if it is equal to null then validation
/// will return true.
///
///
/// The object to check against.
///
/// True if the string is valid, otherwise false.
if (obj == null) {
return true;
}
return validation.isString(obj);
},
isInt: function (obj) {
///
/// Checks to see if a given object is a valid integer.
/// http://stackoverflow.com/questions/3885817/how-to-check-if-a-number-is-float-or-integer
/// http://lawrence.ecorp.net/inet/samples/regexp-validate2.php
///
///
/// The object to check against.
///
/// True if the number is valid, otherwise false.
return (/^[\-+]?\d+$/).test(obj) || (obj === +obj && obj === (obj | 0));
},
optInt: function (obj) {
///
/// Checks to see if a given object is a valid integer.
/// Since the object is optional if it is equal to null then validation
/// will return true.
///
///
/// The object to check against.
///
/// True if the number is valid, otherwise false.
if (obj == null) {
return true;
}
return validation.isInt(obj);
},
isFloat: function (obj) {
///
/// Checks to see if a given object is a valid float.
/// http://stackoverflow.com/questions/3885817/how-to-check-if-a-number-is-float-or-integer
/// http://lawrence.ecorp.net/inet/samples/regexp-validate2.php
///
///
/// The object to check against.
///
/// True if the number is valid, otherwise false.
return (/^[\-+]?\d+(\.\d+)?$/).test(obj) || (obj === +obj && obj !== (obj | 0));
},
optFloat: function (obj) {
///
/// Checks to see if a given object is a valid float.
/// Since the object is optional if it is equal to null then validation
/// will return true.
///
///
/// The object to check against.
///
/// True if the number is valid, otherwise false.
if (obj == null) {
return true;
}
return validation.isFloat(obj);
},
isBool: function (obj) {
///
/// Checks to see if a given object is a valid boolean.
///
///
/// The object to check against.
///
/// True if the boolean is valid, otherwise false.
return (obj === true || obj === "true") || (obj === false || obj === "false");
},
optBool: function (obj) {
///
/// Checks to see if a given object is a valid boolean.
/// Since the object is optional if it is equal to null then validation
/// will return true.
///
///
/// The object to check against.
///
/// True if the boolean is valid, otherwise false.
if (obj == null) {
return true;
}
return validation.isBool(obj);
}
},
methods = {
///
/// A series of private methods.
///
validate: function (param, name) {
///
/// Validates the "data-ga" attributes assigned to the current DOM object.
/// Throws an error if any of the attributes fail validation.
///
///
/// An object containing all the necessary information to allow
/// validation of the given targets properties.
///
///
/// A string containing the name of the parameter to validate.
///
/// The value from the DOM.
var $element = this.$element,
data = $element.data("ga-" + name.toLowerCase()),
isValid;
if (!validation[param.validation]) {
throw new TypeError("Unknown validation type");
}
// Check the value.
isValid = validation[param.validation](data);
if (!isValid) {
throw new Error("object validation error on " + name);
}
// Assign the value.
// Some analytics methods accept numbers as strings so we check the return type.
return data;
},
createArgs: function () {
///
/// Creates the parameter array to pass to the Google gaq/_gaq.push() method.
///
///
/// An object containing all the api relevant information to
/// allow validation of the given targets properties.
///
/// The array of necessary parameters.
var self = this,
event = this.event,
params = [];
// Loop through and build the parameters.
$.each(event, function (key, val) {
var value;
if (key === "event") {
// We don't want to check for the event property in the DOM.
value = val.value;
} else {
// Validate and return the correct value from the DOM.
value = methods.validate.call(self, val, key);
}
params.push(value);
});
// Trim the last null value to reduce data overheads.
while (params[params.length - 1] == null) {
params.pop();
}
return params;
}
},
GoogleAnalytics = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.googleAnalytics.defaults, options);
};
GoogleAnalytics.prototype = {
constructor: GoogleAnalytics,
trackEvent: function () {
var trackedEvent = $.Event("tracked.ga");
this.args = methods.createArgs.call(this);
if (this.options.debug) {
if (window.console && window.console.log) {
// Push the data.
console.log("Pushing Google analytics data.", this.args);
this.$element.trigger(trackedEvent);
}
}
else {
var gaq = window._gaq || window.gaq;
if (gaq) {
// Set the context for our deferred callback.
var self = this;
// Push the data.
$.when(gaq.push(this.args)).done(
function () {
self.$element.trigger(trackedEvent);
// Redirect the location - delayed so that any other page functionality has time to run.
setTimeout(function () {
var href = self.$element.attr("href"),
target = self.$element.attr("target");
if (href && href.indexOf("#") !== 0) {
// IE8 doesn't pass the referrer to the next page
// using window.location. We create a hidden link to
// counter that. Fixes issue #2
// http://stackoverflow.com/a/7917528/427899
var a = document.createElement("a");
if (!a.click) {
// Handle external window requests.
if (target && target === "_blank") {
window.open(href, target);
}
else {
// Old FF and Secure links.
window.location = href;
}
return;
}
a.setAttribute("href", href);
if (target && target === "_blank") {
a.setAttribute("target", target);
}
a.style.display = "none";
document.body.appendChild(a);
a.click();
}
}, 100);
}
);
} else {
throw new ReferenceError("Google Analytics _gaq/gaq object is not defined.");
}
}
}
};
// Plugin definition
$.fn.googleAnalytics = function (options) {
return this.each(function () {
var $this = $(this),
data = $this.data("ga"),
opts = typeof options === "object" ? options : null;
if (!data) {
// Check the data and assign if not present.
$this.data("ga", (data = new GoogleAnalytics(this, opts)));
}
var handler = data.options.handler.toLowerCase(),
// Check for the event attr here as it might be other than the default.
event = data.$element.attr("data-ga-event");
// Overwrite if necessary.
$.extend(data.options, { event: event });
// Build the data as we have nothing there.
// First assign the event.
data.event = $.googleAnalyticsApi[data.options.event];
// Then bind the handler.
if (handler === "load") {
data.trackEvent();
} else {
data.$element.on(handler + ".ga", function (e) {
e.preventDefault();
data.trackEvent();
});
}
});
};
// Define the defaults.
$.fn.googleAnalytics.defaults = {
event: "trackEvent",
handler: "click",
debug: false
};
// Set the public constructor.
$.fn.googleAnalytics.Constructor = GoogleAnalytics;
// Let's get this started.
$(document).on("ready.ga", function () {
// Bind using the custom selector.
$(":ga").each(function () {
$(this).googleAnalytics();
});
});
}(jQuery, window));