var dataLayer = dataLayer||[{}]; Cohorts = (function () { // Enable debugging through the console var Options = { debug: true }; // test scope definition var TEST_SCOPE = { USER:"user", SESSION:"session", HIT:"hit" }; // target URL match type var TARGET_URL_MATCH = { HREF:"href", PATHNAME:"pathname" }; // By default, Cohorts events are pushed onto a data layer (if one is not defined in the test) var dataLayerAdapter = { nameSpace: 'experiments', onInitialize: function(inTest, testName, cohort, analyticsSlot, testScope, newToTest) { var key = 'testSlot'+analyticsSlot; var obj = {}; obj[key] = testName+' '+cohort; dataLayer.push(obj); dataLayer.push({ 'event': 'experiment', 'nameSpace': this.nameSpace, 'testName': testName, 'cohort': cohort, 'testScope': testScope, 'newToTest': newToTest }); }, onEvent: function(testName, cohort, eventName) { dataLayer.push({ 'event': 'event', 'eventCategory': this.nameSpace, 'eventAction': testName, 'eventLabel': cohort, 'eventProperty': scope, // this scope variable is undefined now, is this 'onEvent' method is not in use? 'eventValue': 1, 'eventNonInt': false }); } }; // The main test object var Test = (function () { var cookiePrefix = '_cohorts'; var constructor = function (options) { this.options = Utils.extend({ name: null, cohorts: null, testScope: TEST_SCOPE.USER, analyticsSlot: null, sampleRate: 1.0, storageAdapter: null }, options); this.cohorts = Utils.keys(this.options.cohorts); // Check params if (this.options.name === null) throw ('A name for this test must be specified'); if (this.options.cohorts === null) throw ('Cohorts must be specified for this test'); if (this.cohorts.length < 2) throw ('You must specify at least 2 cohorts for a test'); if (!this.options.analyticsSlot) this.options.analyticsSlot = 5; if (!this.options.storageAdapter) this.options.storageAdapter = dataLayerAdapter; this.run(); }; constructor.prototype = { run: function () { // Determine whether there is forcing of cohorts via the URL and set the cohort var hash = window.location.hash; var preview = false; if (hash.indexOf('#') == 0) hash = hash.slice(1, hash.length); var pairs = hash.split('&'); for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='); var name = pair[0]; var cohort = pair[1]; if (this.options.name == name) { Utils.log('Forcing test ' + name + ' into cohort ' + cohort); this.setCohort(cohort); preview = true; } } // Determine whether user should be in the test & they're on a test page var in_test = this.inTest(); var isExecutable = this.isExecutable(); var newToThisTest = false; var excludeBySample = false; if (in_test === null && !isExecutable){ return; } if (in_test === null && isExecutable) { in_test = Math.random() <= this.options.sampleRate; if (in_test) { newToThisTest = true; } else { excludeBySample = true; } } // Visitors selected into the test are assigned a cohort or set to view an existing one if (in_test || preview) { Utils.log("Test Object ["+this.options.name+"] run."); this.setCookie('in_test', 1); var chosen_cohort, currentCohort = this.getCohort(); if (!currentCohort) { chosen_cohort = this.chooseCohort(); this.setCohort(chosen_cohort); } else { // Returning visitors see the same cohort as last time chosen_cohort = currentCohort; } // Track visitors in the test this.options.storageAdapter.onInitialize(in_test, this.options.name, chosen_cohort, this.options.analyticsSlot, this.options.testScope, newToThisTest); // If using redirectTo handler, redirect now to the URL (unless we're already there) var chosenCohortObject = this.options.cohorts[chosen_cohort]; if (chosenCohortObject.redirectTo&&isExecutable) { var rd = chosenCohortObject.redirectTo; if (Utils.isFunction(chosenCohortObject.redirectTo)) { rd = rd(); } if (rd&&rd.indexOf(window.location.href)<0) { window.location.href = rd; return; } } // Call the onChosen handler, if it exists and run the contents when we want them to run // runNow: TRUE Immediately, else we'll execute onChosen when the DOM is ready if (chosenCohortObject.onChosen&&isExecutable) { if (this.options.runNow) { Utils.log("Test Object ["+this.options.name+"] cohort onChosen ["+chosen_cohort+"] run."); chosenCohortObject.onChosen(); } else { var self = this; Cohorts.domReady(function(){ Utils.log("Test Object ["+self.options.name+"] cohort onChosen ["+chosen_cohort+"] run."); chosenCohortObject.onChosen(); }); } } } else { // For people who were excluded due to sampling, let's exclude them now if (excludeBySample) this.setCookie('in_test', 0); } }, event: function (eventName) { if (this.inTest()) this.options.storageAdapter.onEvent(this.options.name, this.getCohort(), eventName); }, /** * check if a test object is Executable(onChosen and redirect can be performed) * this method will check 4 options, urlMatch, userAgentExclude, cookieMatch, referrerMatch * all options must be matched. * @return {Boolean} */ isExecutable:function() { var targetURLMatch = this.targetURLMatch(); if (!targetURLMatch) { Utils.log("Test Object ["+this.options.name+"] targetURL not matched."); return false; } var userAgentExclude = this.options.userAgentExclude?userAgentUtil.match(this.options.userAgentExclude):false; if (userAgentExclude) { Utils.log("Test Object ["+this.options.name+"] user agent blocked."); return false; } var cookieMatch = this.cookieMatch(); if (!cookieMatch) { Utils.log("Test Object ["+this.options.name+"] cookie not matched."); return false; } var referrerMatch = this.referrerMatch(); if (!referrerMatch) { Utils.log("Test Object ["+this.options.name+"] referrer not matched."); return false; } return true; }, /** * check if current page url matched the test object targetURL option(url split feature) * targetURL is an array of url items * you can specify it like this: * targetURL: [/test1.html/i, /test2.html/i], that means, the test object will run in test1.html or test2.html * or targetURL:[ * { * expression: /gclid=/i, * match:"pathname" ("href"--expression will apply for document.location.href, "pathname"--expression will apply for document.location.pathname) * } * ],that means the regex will apply for document.location.pathname * @return {Boolean} */ targetURLMatch: function() { var urlRegexes = this.options.targetURL; var urlMatch = false; if (!urlRegexes) return true; if (urlRegexes.length <=0) return true; for (var i= 0,c=urlRegexes.length;i= 0 ? tv : undefined; this.mac = dav.indexOf("Macintosh") >= 0; this.ios = /iPhone|iPod|iPad/.test(dua); this.android = parseFloat(dua.split("Android ")[1]) || undefined; this.bb = (dua.indexOf("BlackBerry") >= 0 || dua.indexOf("BB10") >=0)?parseFloat(dua.split("Version/")[1]) || undefined:undefined; this.chrome = parseFloat(dua.split("Chrome/")[1]) || undefined; this.safari = dav.indexOf("Safari")>=0 && !this.chrome ? parseFloat(dav.split("Version/")[1]) : undefined; if (this.chrome) this.chrome = Math.floor(this.chrome); if (this.safari) this.safari = Math.floor(this.safari); if (this.bb) this.bb = Math.floor(this.bb); if (!this.webkit) { if (dua.indexOf("Opera") >= 0) { this.opera = tv >= 9.8 ? parseFloat(dua.split("Version/")[1]) || tv : tv; this.opera = Math.floor(this.opera); } if (dua.indexOf("Gecko") >= 0 && !this.khtml && !this.webkit) { this.mozilla = tv; } if (this.mozilla) { this.ff = parseFloat(dua.split("Firefox/")[1] || dua.split("Minefield/")[1]) || undefined; if (this.ff) this.ff = Math.floor(this.ff); } if (document.all && !this.opera) { var isIE = parseFloat(dav.split("MSIE ")[1]) || undefined; var mode = document.documentMode; if (mode && mode != 5 && Math.floor(isIE) != mode) { isIE = mode; } this.ie = isIE; } } if (dua.match(/(iPhone|iPod|iPad)/)) { var p = RegExp.$1.replace(/P/, 'p'); var v = dua.match(/OS ([\d_]+)/) ? RegExp.$1 : "1"; var os = parseFloat(v.replace(/_/, '.').replace(/_/g, '')); this[p] = os; } this.initialized = true; }, /** * test if current browser's user agent matched the test object userAgentExclude options * @param tua test object userAgentExclude option, it's an array of ua items, * you can specify it like this: * userAgentExclude:["ie", "chrome"], that means test object will run on ie and chrome * userAgentExclude:[{ie:[9, 10]}, {chrome:[25, 26]}] that means test object will run on ie9, ie10, chrome25, chrome26 * support ua names, ie, ff, chrome, safari, opera * we can also specify it by platforms like ios, android * support platform names, ios, android, bb * @return {Boolean}, true -- matched, false -- not matched. */ match: function(tua) { if (!this.initialized) this.init(); if (!tua) return false; if (tua.length <=0) return false; var ret = true; for (var i= 0,c=tua.length;i