/deps/js/mixpanel.js
JavaScript | 3017 lines | 2141 code | 250 blank | 626 comment | 381 complexity | 78ccf84f17ebe11f46b2bb814d958186 MD5 | raw file
Possible License(s): 0BSD
Large files files are truncated, but you can click here to view the full file
- /*
- * Mixpanel JS Library v2.2.0
- *
- * Copyright 2012, Mixpanel, Inc. All Rights Reserved
- * http://mixpanel.com/
- *
- * Includes portions of Underscore.js
- * http://documentcloud.github.com/underscore/
- * (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
- * Released under the MIT License.
- */
- // ==ClosureCompiler==
- // @compilation_level ADVANCED_OPTIMIZATIONS
- // @output_file_name mixpanel-2.2.min.js
- // ==/ClosureCompiler==
- /*
- Will export window.mixpanel
- */
- /*
- SIMPLE STYLE GUIDE:
- this.x == public function
- this._x == internal - only use within this file
- this.__x == private - only use within the class
- Globals should be all caps
- */
- (function (mixpanel) {
- /*
- * Saved references to long variable names, so that closure compiler can
- * minimize file size.
- */
- var ArrayProto = Array.prototype
- , ObjProto = Object.prototype
- , slice = ArrayProto.slice
- , toString = ObjProto.toString
- , hasOwnProperty = ObjProto.hasOwnProperty
- , windowConsole = window.console
- , navigator = window.navigator
- , document = window.document
- , userAgent = navigator.userAgent;
- /*
- * Constants
- */
- /** @const */ var PRIMARY_INSTANCE_NAME = "mixpanel"
- /** @const */ , SET_QUEUE_KEY = "__mps"
- /** @const */ , SET_ONCE_QUEUE_KEY = "__mpso"
- /** @const */ , ADD_QUEUE_KEY = "__mpa"
- /** @const */ , APPEND_QUEUE_KEY = "__mpap"
- /** @const */ , SET_ACTION = "$set"
- /** @const */ , SET_ONCE_ACTION = "$set_once"
- /** @const */ , ADD_ACTION = "$add"
- /** @const */ , APPEND_ACTION = "$append"
- // This key is deprecated, but we want to check for it to see whether aliasing is allowed.
- /** @const */ , PEOPLE_DISTINCT_ID_KEY = "$people_distinct_id"
- /** @const */ , ALIAS_ID_KEY = "__alias"
- /** @const */ , RESERVED_PROPERTIES = [SET_QUEUE_KEY, SET_ONCE_QUEUE_KEY, ADD_QUEUE_KEY, APPEND_QUEUE_KEY, PEOPLE_DISTINCT_ID_KEY, ALIAS_ID_KEY];
- /*
- * Dynamic... constants? Is that an oxymoron?
- */
- var HTTP_PROTOCOL = (("https:" == document.location.protocol) ? "https://" : "http://")
- , SNIPPET_VERSION = (mixpanel && mixpanel['__SV']) || 0
- // http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
- // https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials
- , USE_XHR = (window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest())
- // IE<10 does not support cross-origin XHR's but script tags
- // with defer won't block window.onload; ENQUEUE_REQUESTS
- // should only be true for Opera<12
- , ENQUEUE_REQUESTS = !USE_XHR && (userAgent.indexOf('MSIE') == -1);
- /*
- * Closure-level globals
- */
- var _ = {}
- , DEBUG = false
- , DEFAULT_CONFIG = {
- "api_host": HTTP_PROTOCOL + 'api.mixpanel.com'
- , "cross_subdomain_cookie": true
- , "cookie_name": ""
- , "loaded": function() {}
- , "store_google": true
- , "save_referrer": true
- , "test": false
- , "verbose": false
- , "img": false
- , "track_pageview": true
- , "debug": false
- , "track_links_timeout": 300
- , "cookie_expiration": 365
- , "upgrade": false
- , "disable_cookie": false
- , "secure_cookie": false
- , "ip": true
- }
- , DOM_LOADED = false;
- // UNDERSCORE
- // Embed part of the Underscore Library
- (function() {
- var nativeForEach = ArrayProto.forEach,
- nativeIndexOf = ArrayProto.indexOf,
- nativeIsArray = Array.isArray,
- breaker = {};
- /**
- * @param {*=} obj
- * @param {function(...[*])=} iterator
- * @param {Object=} context
- */
- var each = _.each = function(obj, iterator, context) {
- if (obj == null) return;
- if (nativeForEach && obj.forEach === nativeForEach) {
- obj.forEach(iterator, context);
- } else if (obj.length === +obj.length) {
- for (var i = 0, l = obj.length; i < l; i++) {
- if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
- }
- } else {
- for (var key in obj) {
- if (hasOwnProperty.call(obj, key)) {
- if (iterator.call(context, obj[key], key, obj) === breaker) return;
- }
- }
- }
- };
- _.extend = function(obj) {
- each(slice.call(arguments, 1), function(source) {
- for (var prop in source) {
- if (source[prop] !== void 0) obj[prop] = source[prop];
- }
- });
- return obj;
- };
- _.isArray = nativeIsArray || function(obj) {
- return toString.call(obj) === '[object Array]';
- };
- // from a comment on http://dbj.org/dbj/?p=286
- // fails on only one very rare and deliberate custom object:
- // var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
- _.isFunction = function (f) {
- try {
- return /^\s*\bfunction\b/.test(f);
- } catch (x) {
- return false;
- }
- };
- _.isArguments = function(obj) {
- return !!(obj && hasOwnProperty.call(obj, 'callee'));
- };
- _.toArray = function(iterable) {
- if (!iterable) return [];
- if (iterable.toArray) return iterable.toArray();
- if (_.isArray(iterable)) return slice.call(iterable);
- if (_.isArguments(iterable)) return slice.call(iterable);
- return _.values(iterable);
- };
- _.values = function(obj) {
- var results = [];
- if (obj == null) return results;
- each(obj, function(value) {
- results[results.length] = value;
- });
- return results;
- };
- _.identity = function(value) {
- return value;
- };
- _.include = function(obj, target) {
- var found = false;
- if (obj == null) return found;
- if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
- each(obj, function(value) {
- if (found || (found = (value === target))) { return breaker; }
- });
- return found;
- };
- _.includes = function(str, needle) {
- return str.indexOf(needle) !== -1;
- };
- })();
- // Underscore Addons
- _.inherit = function(subclass, superclass) {
- subclass.prototype = new superclass();
- subclass.prototype.constructor = subclass;
- subclass.superclass = superclass.prototype;
- return subclass;
- };
- _.isObject = function(obj) {
- return (obj === Object(obj) && !_.isArray(obj));
- };
- _.isEmptyObject = function(obj) {
- if (_.isObject(obj)) {
- for (var key in obj) {
- if (hasOwnProperty.call(obj, key)) {
- return false;
- }
- }
- return true;
- }
- return false;
- };
- _.isUndefined = function(obj) {
- return obj === void 0;
- };
- _.isString = function(obj) {
- return toString.call(obj) == '[object String]';
- };
- _.isDate = function(obj) {
- return toString.call(obj) == '[object Date]';
- };
- _.isNumber = function(obj) {
- return toString.call(obj) == '[object Number]';
- };
- _.encodeDates = function(obj) {
- _.each(obj, function(v, k) {
- if (_.isDate(v)) {
- obj[k] = _.formatDate(v);
- } else if (_.isObject(v)) {
- obj[k] = _.encodeDates(v); // recurse
- }
- });
- return obj;
- };
- _.formatDate = function(d) {
- // YYYY-MM-DDTHH:MM:SS in UTC
- function pad(n) {return n < 10 ? '0' + n : n}
- return d.getUTCFullYear() + '-'
- + pad(d.getUTCMonth() + 1) + '-'
- + pad(d.getUTCDate()) + 'T'
- + pad(d.getUTCHours()) + ':'
- + pad(d.getUTCMinutes()) + ':'
- + pad(d.getUTCSeconds());
- };
- _.strip_empty_properties = function(p) {
- var ret = {};
- _.each(p, function(v, k) {
- if (_.isString(v) && v.length > 0) { ret[k] = v; }
- });
- return ret;
- };
- /*
- * this function returns a copy of object after truncating it. If
- * passed an Array or Object it will iterate through obj and
- * truncate all the values recursively.
- */
- _.truncate = function(obj, length) {
- var ret;
- if (typeof(obj) === "string") {
- ret = obj.slice(0, length);
- } else if (_.isArray(obj)) {
- ret = [];
- _.each(obj, function(val) {
- ret.push(_.truncate(val, length));
- });
- } else if (_.isObject(obj)) {
- ret = {};
- _.each(obj, function(val, key) {
- ret[key] = _.truncate(val, length);
- });
- } else {
- ret = obj;
- }
- return ret;
- };
- _.JSONEncode = (function() {
- return function(mixed_val) {
- var indent;
- var value = mixed_val;
- var i;
- var quote = function (string) {
- var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
- var meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- };
- escapable.lastIndex = 0;
- return escapable.test(string) ?
- '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === 'string' ? c :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- };
- var str = function(key, holder) {
- var gap = '';
- var indent = ' ';
- var i = 0; // The loop counter.
- var k = ''; // The member key.
- var v = ''; // The member value.
- var length = 0;
- var mind = gap;
- var partial = [];
- var value = holder[key];
- // If the value has a toJSON method, call it to obtain a replacement value.
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
- // What happens next depends on the value's type.
- switch (typeof value) {
- case 'string':
- return quote(value);
- case 'number':
- // JSON numbers must be finite. Encode non-finite numbers as null.
- return isFinite(value) ? String(value) : 'null';
- case 'boolean':
- case 'null':
- // If the value is a boolean or null, convert it to a string. Note:
- // typeof null does not produce 'null'. The case is included here in
- // the remote chance that this gets fixed someday.
- return String(value);
- case 'object':
- // If the type is 'object', we might be dealing with an object or an array or
- // null.
- // Due to a specification blunder in ECMAScript, typeof null is 'object',
- // so watch out for that case.
- if (!value) {
- return 'null';
- }
- // Make an array to hold the partial results of stringifying this object value.
- gap += indent;
- partial = [];
- // Is the value an array?
- if (toString.apply(value) === '[object Array]') {
- // The value is an array. Stringify every element. Use null as a placeholder
- // for non-JSON values.
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
- // Join all of the elements together, separated with commas, and wrap them in
- // brackets.
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
- // Iterate through all of the keys in the object.
- for (k in value) {
- if (hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- // Join all of the member texts together, separated with commas,
- // and wrap them in braces.
- v = partial.length === 0 ? '{}' :
- gap ? '{' + partial.join(',') + '' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- };
- // Make a fake root object containing our value under the key of ''.
- // Return the result of stringifying the value.
- return str('', {
- '': value
- });
- };
- })();
- _.JSONDecode = (function() { // https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
- var at, // The index of the current character
- ch, // The current character
- escapee = {
- '"': '"',
- '\\': '\\',
- '/': '/',
- 'b': '\b',
- 'f': '\f',
- 'n': '\n',
- 'r': '\r',
- 't': '\t'
- },
- text,
- error = function (m) {
- throw {
- name: 'SyntaxError',
- message: m,
- at: at,
- text: text
- };
- },
- next = function (c) {
- // If a c parameter is provided, verify that it matches the current character.
- if (c && c !== ch) {
- error("Expected '" + c + "' instead of '" + ch + "'");
- }
- // Get the next character. When there are no more characters,
- // return the empty string.
- ch = text.charAt(at);
- at += 1;
- return ch;
- },
- number = function () {
- // Parse a number value.
- var number,
- string = '';
- if (ch === '-') {
- string = '-';
- next('-');
- }
- while (ch >= '0' && ch <= '9') {
- string += ch;
- next();
- }
- if (ch === '.') {
- string += '.';
- while (next() && ch >= '0' && ch <= '9') {
- string += ch;
- }
- }
- if (ch === 'e' || ch === 'E') {
- string += ch;
- next();
- if (ch === '-' || ch === '+') {
- string += ch;
- next();
- }
- while (ch >= '0' && ch <= '9') {
- string += ch;
- next();
- }
- }
- number = +string;
- if (!isFinite(number)) {
- error("Bad number");
- } else {
- return number;
- }
- },
- string = function () {
- // Parse a string value.
- var hex,
- i,
- string = '',
- uffff;
- // When parsing for string values, we must look for " and \ characters.
- if (ch === '"') {
- while (next()) {
- if (ch === '"') {
- next();
- return string;
- }
- if (ch === '\\') {
- next();
- if (ch === 'u') {
- uffff = 0;
- for (i = 0; i < 4; i += 1) {
- hex = parseInt(next(), 16);
- if (!isFinite(hex)) {
- break;
- }
- uffff = uffff * 16 + hex;
- }
- string += String.fromCharCode(uffff);
- } else if (typeof escapee[ch] === 'string') {
- string += escapee[ch];
- } else {
- break;
- }
- } else {
- string += ch;
- }
- }
- }
- error("Bad string");
- },
- white = function () {
- // Skip whitespace.
- while (ch && ch <= ' ') {
- next();
- }
- },
- word = function () {
- // true, false, or null.
- switch (ch) {
- case 't':
- next('t');
- next('r');
- next('u');
- next('e');
- return true;
- case 'f':
- next('f');
- next('a');
- next('l');
- next('s');
- next('e');
- return false;
- case 'n':
- next('n');
- next('u');
- next('l');
- next('l');
- return null;
- }
- error("Unexpected '" + ch + "'");
- },
- value, // Placeholder for the value function.
- array = function () {
- // Parse an array value.
- var array = [];
- if (ch === '[') {
- next('[');
- white();
- if (ch === ']') {
- next(']');
- return array; // empty array
- }
- while (ch) {
- array.push(value());
- white();
- if (ch === ']') {
- next(']');
- return array;
- }
- next(',');
- white();
- }
- }
- error("Bad array");
- },
- object = function () {
- // Parse an object value.
- var key,
- object = {};
- if (ch === '{') {
- next('{');
- white();
- if (ch === '}') {
- next('}');
- return object; // empty object
- }
- while (ch) {
- key = string();
- white();
- next(':');
- if (Object.hasOwnProperty.call(object, key)) {
- error('Duplicate key "' + key + '"');
- }
- object[key] = value();
- white();
- if (ch === '}') {
- next('}');
- return object;
- }
- next(',');
- white();
- }
- }
- error("Bad object");
- };
- value = function () {
- // Parse a JSON value. It could be an object, an array, a string,
- // a number, or a word.
- white();
- switch (ch) {
- case '{':
- return object();
- case '[':
- return array();
- case '"':
- return string();
- case '-':
- return number();
- default:
- return ch >= '0' && ch <= '9' ? number() : word();
- }
- };
- // Return the json_parse function. It will have access to all of the
- // above functions and variables.
- return function (source) {
- var result;
- text = source;
- at = 0;
- ch = ' ';
- result = value();
- white();
- if (ch) {
- error("Syntax error");
- }
- return result;
- };
- })();
- _.base64Encode = function(data) {
- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];
- if (!data) {
- return data;
- }
- data = _.utf8Encode(data);
- do { // pack three octets into four hexets
- o1 = data.charCodeAt(i++);
- o2 = data.charCodeAt(i++);
- o3 = data.charCodeAt(i++);
- bits = o1<<16 | o2<<8 | o3;
- h1 = bits>>18 & 0x3f;
- h2 = bits>>12 & 0x3f;
- h3 = bits>>6 & 0x3f;
- h4 = bits & 0x3f;
- // use hexets to index into b64, and append result to encoded string
- tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
- } while (i < data.length);
- enc = tmp_arr.join('');
- switch( data.length % 3 ){
- case 1:
- enc = enc.slice(0, -2) + '==';
- break;
- case 2:
- enc = enc.slice(0, -1) + '=';
- break;
- }
- return enc;
- };
- _.utf8Encode = function(string) {
- string = (string+'').replace(/\r\n/g, "\n").replace(/\r/g, "\n");
- var utftext = "",
- start,
- end;
- var stringl = 0,
- n;
- start = end = 0;
- stringl = string.length;
- for (n = 0; n < stringl; n++) {
- var c1 = string.charCodeAt(n);
- var enc = null;
- if (c1 < 128) {
- end++;
- } else if((c1 > 127) && (c1 < 2048)) {
- enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
- } else {
- enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
- }
- if (enc !== null) {
- if (end > start) {
- utftext += string.substring(start, end);
- }
- utftext += enc;
- start = end = n+1;
- }
- }
- if (end > start) {
- utftext += string.substring(start, string.length);
- }
- return utftext;
- };
- _.UUID = (function() {
- // Time/ticks information
- // 1*new Date() is a cross browser version of Date.now()
- var T = function() {
- var d = 1*new Date()
- , i = 0;
- // this while loop figures how many browser ticks go by
- // before 1*new Date() returns a new number, ie the amount
- // of ticks that go by per millisecond
- while (d == 1*new Date()) { i++; }
- return d.toString(16) + i.toString(16);
- };
- // Math.Random entropy
- var R = function() {
- return Math.random().toString(16).replace('.','');
- };
- // User agent entropy
- // This function takes the user agent string, and then xors
- // together each sequence of 8 bytes. This produces a final
- // sequence of 8 bytes which it returns as hex.
- var UA = function(n) {
- var ua = userAgent, i, ch, buffer = [], ret = 0;
- function xor(result, byte_array) {
- var j, tmp = 0;
- for (j = 0; j < byte_array.length; j++) {
- tmp |= (buffer[j] << j*8);
- }
- return result ^ tmp;
- }
- for (i = 0; i < ua.length; i++) {
- ch = ua.charCodeAt(i);
- buffer.unshift(ch & 0xFF);
- if (buffer.length >= 4) {
- ret = xor(ret, buffer);
- buffer = [];
- }
- }
- if (buffer.length > 0) { ret = xor(ret, buffer); }
- return ret.toString(16);
- };
- return function() {
- var se = (screen.height*screen.width).toString(16);
- return (T()+"-"+R()+"-"+UA()+"-"+se+"-"+T());
- };
- })();
- // _.isBlockedUA()
- // This is to block various web spiders from executing our JS and
- // sending false tracking data
- _.isBlockedUA = function() {
- var a = userAgent;
- if (/(google web preview|baiduspider|yandexbot)/i.test(a)) {
- return true;
- }
- return false;
- };
- /**
- * @param {Object=} formdata
- * @param {string=} arg_separator
- */
- _.HTTPBuildQuery = function(formdata, arg_separator) {
- var key, use_val, use_key, tmp_arr = [];
- if (typeof(arg_separator) === "undefined") {
- arg_separator = '&';
- }
- _.each(formdata, function(val, key) {
- use_val = encodeURIComponent(val.toString());
- use_key = encodeURIComponent(key);
- tmp_arr[tmp_arr.length] = use_key + '=' + use_val;
- });
- return tmp_arr.join(arg_separator);
- };
- _.getQueryParam = function(url, param) {
- // Expects a raw URL
- param = param.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
- var regexS = "[\\?&]" + param + "=([^&#]*)",
- regex = new RegExp( regexS ),
- results = regex.exec(url);
- if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
- return '';
- } else {
- return decodeURIComponent(results[1]).replace(/\+/g, ' ');
- }
- };
- // _.cookie
- // Methods partially borrowed from quirksmode.org/js/cookies.html
- _.cookie = {
- get: function(name) {
- var nameEQ = name + "=";
- var ca = document.cookie.split(';');
- for(var i=0;i < ca.length;i++) {
- var c = ca[i];
- while (c.charAt(0)==' ') c = c.substring(1,c.length);
- if (c.indexOf(nameEQ) == 0) return decodeURIComponent(c.substring(nameEQ.length,c.length));
- }
- return null;
- },
- parse: function(name) {
- var cookie;
- try {
- cookie = _.JSONDecode(_.cookie.get(name)) || {};
- } catch (err) {}
- return cookie;
- },
- set: function(name, value, days, cross_subdomain, is_secure) {
- var cdomain = "", expires = "", secure = "";
- if (cross_subdomain) {
- var matches = document.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i)
- , domain = matches ? matches[0] : '';
- cdomain = ((domain) ? "; domain=." + domain : "");
- }
- if (days) {
- var date = new Date();
- date.setTime(date.getTime()+(days*24*60*60*1000));
- expires = "; expires=" + date.toGMTString();
- }
- if (is_secure) {
- secure = "; secure";
- }
- document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain + secure;
- },
- remove: function(name, cross_subdomain) {
- _.cookie.set(name, '', -1, cross_subdomain);
- }
- };
- _.register_event = (function() {
- // written by Dean Edwards, 2005
- // with input from Tino Zijdel - crisp@xs4all.nl
- // with input from Carl Sverre - mail@carlsverre.com
- // with input from Mixpanel
- // http://dean.edwards.name/weblog/2005/10/add-event/
- // https://gist.github.com/1930440
- /**
- * @param {Object} element
- * @param {string} type
- * @param {function(...[*])} handler
- * @param {boolean=} oldSchool
- */
- var register_event = function(element, type, handler, oldSchool) {
- if (!element) {
- console.error("No valid element provided to register_event");
- return;
- }
- if (element.addEventListener && !oldSchool) {
- element.addEventListener(type, handler, false);
- } else {
- var ontype = 'on' + type;
- var old_handler = element[ontype]; // can be undefined
- element[ontype] = makeHandler(element, handler, old_handler);
- }
- };
- function makeHandler(element, new_handler, old_handlers) {
- var handler = function(event) {
- event = event || fixEvent(window.event);
- // this basically happens in firefox whenever another script
- // overwrites the onload callback and doesn't pass the event
- // object to previously defined callbacks. All the browsers
- // that don't define window.event implement addEventListener
- // so the dom_loaded handler will still be fired as usual.
- if (!event) { return undefined; }
- var ret = true;
- var old_result, new_result;
- if (_.isFunction(old_handlers)) {
- old_result = old_handlers(event);
- }
- new_result = new_handler.call(element, event);
- if ((false === old_result) || (false === new_result)) {
- ret = false;
- }
- return ret;
- };
- return handler;
- };
- function fixEvent(event) {
- if (event) {
- event.preventDefault = fixEvent.preventDefault;
- event.stopPropagation = fixEvent.stopPropagation;
- }
- return event;
- };
- fixEvent.preventDefault = function() {
- this.returnValue = false;
- };
- fixEvent.stopPropagation = function() {
- this.cancelBubble = true;
- };
- return register_event;
- })();
- _.dom_query = (function() {
- /* document.getElementsBySelector(selector)
- - returns an array of element objects from the current document
- matching the CSS selector. Selectors can contain element names,
- class names and ids and can be nested. For example:
- elements = document.getElementsBySelector('div#main p a.external')
- Will return an array of all 'a' elements with 'external' in their
- class attribute that are contained inside 'p' elements that are
- contained inside the 'div' element which has id="main"
- New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
- See http://www.w3.org/TR/css3-selectors/#attribute-selectors
- Version 0.4 - Simon Willison, March 25th 2003
- -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
- -- Opera 7 fails
- Version 0.5 - Carl Sverre, Jan 7th 2013
- -- Now uses jQuery-esque `hasClass` for testing class name
- equality. This fixes a bug related to '-' characters being
- considered not part of a 'word' in regex.
- */
- function getAllChildren(e) {
- // Returns all children of element. Workaround required for IE5/Windows. Ugh.
- return e.all ? e.all : e.getElementsByTagName('*');
- }
- var bad_whitespace = /[\t\r\n]/g;
- function hasClass(elem, selector) {
- var className = " " + selector + " ";
- return ((" " + elem.className + " ").replace(bad_whitespace, " ").indexOf(className) >= 0);
- }
- function getElementsBySelector(selector) {
- // Attempt to fail gracefully in lesser browsers
- if (!document.getElementsByTagName) {
- return new Array();
- }
- // Split selector in to tokens
- var tokens = selector.split(' ');
- var token;
- var currentContext = new Array(document);
- for (var i = 0; i < tokens.length; i++) {
- token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');
- if (token.indexOf('#') > -1) {
- // Token is an ID selector
- var bits = token.split('#');
- var tagName = bits[0];
- var id = bits[1];
- var element = document.getElementById(id);
- if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
- // element not found or tag with that ID not found, return false
- return new Array();
- }
- // Set currentContext to contain just this element
- currentContext = new Array(element);
- continue; // Skip to next token
- }
- if (token.indexOf('.') > -1) {
- // Token contains a class selector
- var bits = token.split('.');
- var tagName = bits[0];
- var className = bits[1];
- if (!tagName) {
- tagName = '*';
- }
- // Get elements matching tag, filter them for class selector
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements;
- if (tagName == '*') {
- elements = getAllChildren(currentContext[h]);
- } else {
- elements = currentContext[h].getElementsByTagName(tagName);
- }
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = new Array;
- var currentContextIndex = 0;
- for (var k = 0; k < found.length; k++) {
- if (found[k].className
- && _.isString(found[k].className) // some SVG elements have classNames which are not strings
- && hasClass(found[k], className)
- ) {
- currentContext[currentContextIndex++] = found[k];
- }
- }
- continue; // Skip to next token
- }
- // Code to deal with attribute selectors
- if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
- var tagName = RegExp.$1;
- var attrName = RegExp.$2;
- var attrOperator = RegExp.$3;
- var attrValue = RegExp.$4;
- if (!tagName) {
- tagName = '*';
- }
- // Grab all of the tagName elements within current context
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements;
- if (tagName == '*') {
- elements = getAllChildren(currentContext[h]);
- } else {
- elements = currentContext[h].getElementsByTagName(tagName);
- }
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = new Array;
- var currentContextIndex = 0;
- var checkFunction; // This function will be used to filter the elements
- switch (attrOperator) {
- case '=': // Equality
- checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
- break;
- case '~': // Match one of space seperated words
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
- break;
- case '|': // Match start with value followed by optional hyphen
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
- break;
- case '^': // Match starts with value
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
- break;
- case '$': // Match ends with value - fails with "Warning" in Opera 7
- checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
- break;
- case '*': // Match ends with value
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
- break;
- default :
- // Just test for existence of attribute
- checkFunction = function(e) { return e.getAttribute(attrName); };
- }
- currentContext = new Array;
- currentContextIndex = 0;
- for (var k = 0; k < found.length; k++) {
- if (checkFunction(found[k])) {
- currentContext[currentContextIndex++] = found[k];
- }
- }
- // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
- continue; // Skip to next token
- }
- // If we get here, token is JUST an element (not a class or ID selector)
- tagName = token;
- var found = new Array;
- var foundCount = 0;
- for (var h = 0; h < currentContext.length; h++) {
- var elements = currentContext[h].getElementsByTagName(tagName);
- for (var j = 0; j < elements.length; j++) {
- found[foundCount++] = elements[j];
- }
- }
- currentContext = found;
- }
- return currentContext;
- };
- return getElementsBySelector;
- })();
- _.info = {
- campaignParams: function() {
- var campaign_keywords = 'utm_source utm_medium utm_campaign utm_content utm_term'.split(' ')
- , kw = ''
- , params = {};
- _.each(campaign_keywords, function(kwkey) {
- kw = _.getQueryParam(document.URL, kwkey);
- if (kw.length) {
- params[kwkey] = kw;
- }
- });
- return params;
- },
- searchEngine: function(referrer) {
- if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
- return 'google';
- } else if (referrer.search('https?://(.*)bing.com') === 0) {
- return 'bing';
- } else if (referrer.search('https?://(.*)yahoo.com') === 0) {
- return 'yahoo';
- } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) {
- return 'duckduckgo';
- } else {
- return null;
- }
- },
- searchInfo: function(referrer) {
- var search = _.info.searchEngine(referrer)
- , param = (search != "yahoo") ? "q" : "p"
- , ret = {};
- if (search !== null) {
- ret["$search_engine"] = search;
- var keyword = _.getQueryParam(referrer, param);
- if (keyword.length) {
- ret["mp_keyword"] = keyword;
- }
- }
- return ret;
- },
- /**
- * This function detects which browser is running this script.
- * The order of the checks are important since many user agents
- * include key words used in later checks.
- */
- browser: function() {
- var ua = userAgent
- , vend = navigator.vendor || ''; // vendor is undefined for at least IE9
- if (window.opera) {
- if (_.includes(ua, "Mini")) {
- return "Opera Mini";
- }
- return "Opera";
- } else if (/(BlackBerry|PlayBook|BB10)/i.test(ua)) {
- return 'BlackBerry';
- } else if (_.includes(ua, "Chrome")) {
- return "Chrome";
- } else if (_.includes(vend, "Apple")) {
- if (_.includes(ua, "Mobile")) {
- return "Mobile Safari";
- }
- return "Safari";
- } else if (_.includes(ua, "Android")) {
- return "Android Mobile";
- } else if (_.includes(ua, "Konqueror")) {
- return "Konqueror";
- } else if (_.includes(ua, "Firefox")) {
- return "Firefox";
- } else if (_.includes(ua, "MSIE")) {
- return "Internet Explorer";
- } else if (_.includes(ua, "Gecko")) {
- return "Mozilla";
- } else {
- return "";
- }
- },
- os: function() {
- var a = userAgent;
- if (/Windows/i.test(a)) {
- if (/Phone/.test(a)) { return 'Windows Mobile'; }
- return 'Windows';
- } else if (/(iPhone|iPad|iPod)/.test(a)) {
- return 'iOS';
- } else if (/Android/.test(a)) {
- return 'Android';
- } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
- return 'BlackBerry';
- } else if (/Mac/i.test(a)) {
- return 'Mac OS X';
- } else if (/Linux/.test(a)) {
- return 'Linux';
- } else {
- return '';
- }
- },
- device: function() {
- var a = userAgent;
- if (/iPhone/.test(a)) {
- return 'iPhone';
- } else if (/iPad/.test(a)) {
- return 'iPad';
- } else if (/iPod/.test(a)) {
- return 'iPod Touch';
- } else if (/(BlackBerry|PlayBook|BB10)/i.test(a)) {
- return 'BlackBerry';
- } else if (/Windows Phone/i.test(a)) {
- return 'Windows Phone';
- } else if (/Android/.test(a)) {
- return 'Android';
- } else {
- return '';
- }
- },
- referringDomain: function(referrer) {
- var split = referrer.split("/");
- if (split.length >= 3) {
- return split[2];
- }
- return "";
- },
- properties: function() {
- return _.strip_empty_properties({
- '$os': _.info.os(),
- '$browser': _.info.browser(),
- '$referrer': document.referrer,
- '$referring_domain': _.info.referringDomain(document.referrer),
- '$device': _.info.device(),
- 'mp_lib': 'web'
- });
- },
- people_properties: function() {
- return _.strip_empty_properties({
- '$os': _.info.os(),
- '$browser': _.info.browser()
- });
- },
- pageviewInfo: function(page) {
- return _.strip_empty_properties({
- 'mp_page': page
- , 'mp_referrer': document.referrer
- , 'mp_browser': _.info.browser()
- , 'mp_platform': _.info.os()
- });
- }
- };
- // Console override
- var console = {
- /** @type {function(...[*])} */
- log: function() {
- if (DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
- try {
- windowConsole.log.apply(windowConsole, arguments);
- } catch(err) {
- _.each(arguments, function(arg) {
- windowConsole.log(arg);
- });
- }
- }
- },
- /** @type {function(...[*])} */
- error: function() {
- if (DEBUG && !_.isUndefined(windowConsole) && windowConsole) {
- var args = ["Mixpanel error:"].concat(_.toArray(arguments));
- try {
- windowConsole.error.apply(windowConsole, args);
- } catch(err) {
- _.each(args, function(arg) {
- windowConsole.error(arg);
- });
- }
- }
- },
- /** @type {function(...[*])} */
- critical: function() {
- if (!_.isUndefined(windowConsole) && windowConsole) {
- var args = ["Mixpanel error:"].concat(_.toArray(arguments));
- try {
- windowConsole.error.apply(windowConsole, args);
- } catch(err) {
- _.each(args, function(arg) {
- windowConsole.error(arg);
- });
- }
- }
- }
- };
- /**
- * DomTracker Object
- * @constructor
- */
- var DomTracker = function() {};
- // interface
- DomTracker.prototype.create_properties = function() {};
- DomTracker.prototype.event_handler = function() {};
- DomTracker.prototype.after_track_handler = function() {};
- DomTracker.prototype.init = function(mixpanel_instance) {
- this.mp = mixpanel_instance;
- return this;
- };
- /**
- * @param {string} query
- * @param {string} event_name
- * @param {Object=} properties
- * @param {function(...[*])=} user_callback
- */
- DomTracker.prototype.track = function(query, event_name, properties, user_callback) {
- var that = this
- , elements = _.dom_query(query);
- if (elements.length == 0) {
- console.error("The DOM query (" + query + ") returned 0 elements");
- return;
- }
- _.each(elements, function(element) {
- _.register_event(element, this.override_event, function(e) {
- var options = {}
- , props = that.create_properties(properties, this)
- , timeout = that.mp.get_config("track_links_timeout");
- that.event_handler(e, this, options);
- // in case the mixpanel servers don't get back to us in time
- window.setTimeout(that.track_callback(user_callback, props, options, true), timeout);
- // fire the tracking event
- that.mp.track(event_name, props, that.track_callback(user_callback, props, options));
- });
- }, this);
- return true;
- };
- /**
- * @param {function(...[*])} user_callback
- * @param {Object} props
- * @param {boolean=} timeout_occured
- */
- DomTracker.prototype.track_callback = function(user_callback, props, options, timeout_occured) {
- timeout_occured = timeout_occured || false;
- …
Large files files are truncated, but you can click here to view the full file