diff --git a/src/autotrack.js b/src/autotrack.js index ee47e0b0..b6913df0 100644 --- a/src/autotrack.js +++ b/src/autotrack.js @@ -302,7 +302,7 @@ var autotrack = { } // crawl up to max of 5 nodes to populate text content if (!elementText && idx < 5 && el.textContent) { - var textContent = _.trim(el.textContent); + var textContent = el.textContent.trim(); if (textContent) { elementText = textContent.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ').substring(0, 255); } @@ -346,10 +346,10 @@ var autotrack = { }, _addDomEventHandlers: function(instance) { - var handler = _.bind(function(e) { + var handler = function(e) { e = e || window.event; this._trackEvent(e, instance); - }, this); + }.bind(this); _.register_event(document, 'submit', handler, false, true); _.register_event(document, 'change', handler, false, true); _.register_event(document, 'click', handler, false, true); @@ -372,7 +372,7 @@ var autotrack = { this._initializedTokens.push(token); if (!this._maybeLoadEditor(instance)) { // don't autotrack actions when the editor is enabled - var parseDecideResponse = _.bind(function(response) { + var parseDecideResponse = function(response) { if (response && response['config'] && response['config']['enable_collect_everything'] === true) { if (response['custom_properties']) { @@ -388,7 +388,7 @@ var autotrack = { } else { instance['__autotrack_enabled'] = false; } - }, this); + }.bind(this); instance._send_request( instance.get_config('api_host') + '/decide/', { diff --git a/src/utils.js b/src/utils.js index 54fc42b3..503e3f0d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -11,15 +11,12 @@ if (typeof(window) === 'undefined') { win = window; } - - /* * Saved references to long variable names, so that closure compiler can * minimize file size. */ var ArrayProto = Array.prototype, - FuncProto = Function.prototype, ObjProto = Object.prototype, slice = ArrayProto.slice, toString = ObjProto.toString, @@ -29,18 +26,12 @@ var ArrayProto = Array.prototype, document = win.document, userAgent = navigator.userAgent; -var nativeBind = FuncProto.bind, - nativeForEach = ArrayProto.forEach, +var nativeForEach = ArrayProto.forEach, nativeIndexOf = ArrayProto.indexOf, nativeIsArray = Array.isArray, breaker = {}; -var _ = { - trim: function(str) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill - return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); - } -}; +var _ = {}; // Console override var console = { @@ -84,39 +75,10 @@ var console = { } }; - -// UNDERSCORE -// Embed part of the Underscore Library -_.bind = function(func, context) { - var args, bound; - if (nativeBind && func.bind === nativeBind) { - return nativeBind.apply(func, slice.call(arguments, 1)); - } - if (!_.isFunction(func)) { - throw new TypeError(); - } - args = slice.call(arguments, 2); - bound = function() { - if (!(this instanceof bound)) { - return func.apply(context, args.concat(slice.call(arguments))); - } - var ctor = {}; - ctor.prototype = func.prototype; - var self = new ctor(); - ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) { - return result; - } - return self; - }; - return bound; -}; - _.bind_instance_methods = function(obj) { for (var func in obj) { if (typeof(obj[func]) === 'function') { - obj[func] = _.bind(obj[func], obj); + obj[func] = obj[func].bind(obj); } } }; @@ -219,10 +181,6 @@ _.values = function(obj) { return results; }; -_.identity = function(value) { - return value; -}; - _.include = function(obj, target) { var found = false; if (obj === null) { @@ -298,13 +256,6 @@ _.encodeDates = function(obj) { return obj; }; -_.timestamp = function() { - Date.now = Date.now || function() { - return +new Date; - }; - return Date.now(); -}; - _.formatDate = function(d) { // YYYY-MM-DDTHH:MM:SS in UTC function pad(n) { @@ -379,440 +330,14 @@ _.truncate = function(obj, length) { return ret; }; -_.JSONEncode = (function() { - return function(mixed_val) { - var value = mixed_val; - 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; // eslint-disable-line no-control-regex - 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; - }; -})(); +_.JSONEncode = JSON.stringify; +_.JSONDecode = JSON.parse; _.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; + return btoa(unescape(encodeURIComponent(String(data)))); }; _.UUID = (function() { @@ -1128,199 +653,14 @@ _.register_event = (function() { 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 []; - } - // Split selector in to tokens - var tokens = selector.split(' '); - var token, bits, tagName, found, foundCount, i, j, k, elements, currentContextIndex; - var currentContext = [document]; - for (i = 0; i < tokens.length; i++) { - token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, ''); - if (token.indexOf('#') > -1) { - // Token is an ID selector - bits = token.split('#'); - 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 []; - } - // Set currentContext to contain just this element - currentContext = [element]; - continue; // Skip to next token - } - if (token.indexOf('.') > -1) { - // Token contains a class selector - bits = token.split('.'); - tagName = bits[0]; - var className = bits[1]; - if (!tagName) { - tagName = '*'; - } - // Get elements matching tag, filter them for class selector - found = []; - foundCount = 0; - for (j = 0; j < currentContext.length; j++) { - if (tagName == '*') { - elements = getAllChildren(currentContext[j]); - } else { - elements = currentContext[j].getElementsByTagName(tagName); - } - for (k = 0; k < elements.length; k++) { - found[foundCount++] = elements[k]; - } - } - currentContext = []; - currentContextIndex = 0; - for (j = 0; j < found.length; j++) { - if (found[j].className && - _.isString(found[j].className) && // some SVG elements have classNames which are not strings - hasClass(found[j], className) - ) { - currentContext[currentContextIndex++] = found[j]; - } - } - continue; // Skip to next token - } - // Code to deal with attribute selectors - var token_match = token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/); - if (token_match) { - tagName = token_match[1]; - var attrName = token_match[2]; - var attrOperator = token_match[3]; - var attrValue = token_match[4]; - if (!tagName) { - tagName = '*'; - } - // Grab all of the tagName elements within current context - found = []; - foundCount = 0; - for (j = 0; j < currentContext.length; j++) { - if (tagName == '*') { - elements = getAllChildren(currentContext[j]); - } else { - elements = currentContext[j].getElementsByTagName(tagName); - } - for (k = 0; k < elements.length; k++) { - found[foundCount++] = elements[k]; - } - } - currentContext = []; - 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 = []; - currentContextIndex = 0; - for (j = 0; j < found.length; j++) { - if (checkFunction(found[j])) { - currentContext[currentContextIndex++] = found[j]; - } - } - // 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; - found = []; - foundCount = 0; - for (j = 0; j < currentContext.length; j++) { - elements = currentContext[j].getElementsByTagName(tagName); - for (k = 0; k < elements.length; k++) { - found[foundCount++] = elements[k]; - } - } - currentContext = found; - } - return currentContext; +_.dom_query = function(query) { + if (_.isElement(query)) { + return [query]; + } else if (_.isObject(query) && !_.isUndefined(query.length)) { + return query; } - - return function(query) { - if (_.isElement(query)) { - return [query]; - } else if (_.isObject(query) && !_.isUndefined(query.length)) { - return query; - } else { - return getElementsBySelector.call(this, query); - } - }; -})(); + return _.toArray(document.querySelectorAll(query)); +}; _.info = { campaignParams: function() {