(function(angular) { // Just in case... if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement, fromIndex) { if ( this === undefined || this === null ) { throw new TypeError( '"this" is null or not defined' ); } var length = this.length >>> 0; // Hack to convert object.length to a UInt32 fromIndex = +fromIndex || 0; if (Math.abs(fromIndex) === Infinity) { fromIndex = 0; } if (fromIndex < 0) { fromIndex += length; if (fromIndex < 0) { fromIndex = 0; } } for (;fromIndex < length; fromIndex++) { if (this[fromIndex] === searchElement) { return fromIndex; } } return -1; }; } angular.module('mvdSixpack', [ 'ipCookie' ]) .provider('sixpack', function() { var $body , _tests = [] , _choices = {} , _opts = { baseUrl: '', debug: false, } , sp = window.sixpack; this.setOptions = function (options) { angular.extend(_opts, options || {}); } this.$get = ['ipCookie','$timeout', '$log', function(ipCookie, $timeout, $log) { var _cookiePrefix = 'sixpack-' , _session , _clientId; var _getOrInitSession = function () { if (!_session) { if (_clientId = ipCookie(_cookiePrefix + 'clientId')) { _session = new sp.Session({client_id:_clientId, base_url:_opts.baseUrl}); } else { _session = new sp.Session({client_id:_opts.client_id, base_url:_opts.baseUrl}); _clientId = _session.client_id; ipCookie(_cookiePrefix + 'clientId', _clientId); } if (_opts.debug) { $log.debug('[sixpack] Initialized session with clientId', _clientId, 'and base url', _opts.baseUrl); }; }; return _session; } var methods = { participate : function (testName, variations, callback) { if (_tests.indexOf(testName) < 0) { _tests.push(testName); } else if (angular.isDefined(_choices[testName])) { var res = _choices[testName]; if (res === false) { // Still loading $timeout(function () { methods.participate(testName, variations, callback); }, 50); return; }; if (_opts.debug) { $log.info('[sixpack] Using already chosen variation for test', testName, res); }; $timeout(function () { callback(res.alternative.name, res); }); return; } _choices[testName] = false; var session = _getOrInitSession(); if (_opts.debug) { $log.info('[sixpack] Getting choice for', testName, 'out of', variations); }; session.participate(testName, variations, function (err, res) { if (err) { if (_opts.debug) { $log.warn('[sixpack] Received error', err); }; $timeout(function () { callback(false); }); delete _choices[testName]; return; }; _choices[testName] = res; var choice = res.alternative.name; if (_opts.debug) { $log.info('[sixpack] Alternative chosen:', choice); $log.debug('[sixpack] Full response', res); }; if (!$body) { $body = angular.element(document).find('body'); }; $body.addClass('sixpack-'+testName+' sixpack-'+testName+'-'+choice); $timeout(function() { callback(choice, res); }); }); }, // Register a 'conversion'. If no testName, will call for all active tests // Takes an optional callback that receives the raw response from sixpack (or undefined on error) convert : function (testName, kpi, callback) { if (typeof kpi === 'function') { callback = kpi; kpi = null; } var session = _getOrInitSession(); if (!testName) { if (_opts.debug) { $log.info("[sixpack] Recording conversion for all tests", _tests); }; for (var i = 0, ii = _tests.length; i < ii; i++) { var test = _tests[i] , results = []; session.convert(test, kpi, function (err, res) { results.push(res); if (err && _opts.debug) { $log.warn("[sixpack] Error recording conversion for", test, err); }; if (results.length == ii) { if (_opts.debug) { $log.debug('[sixpack] All results:', results); }; if (callback) { $timeout(function () { callback(results); }); } }; }); } } else { if (_opts.debug) { $log.info("[sixpack] Recording conversion for", testName); }; session.convert(testName, kpi, function (err, res) { if (err && _opts.debug) { $log.warn('[sixpack] Error recording conversion:', err); } else if (_opts.debug) { $log.debug('[sixpack] Conversion result:', res); }; if (callback) { $timeout(function () { callback(res); }); } }); } } } return methods; }]; }) .directive('sixpackSwitch', ['sixpack', function(sixpack) { return { controller : ['$element', function($element) { var ctrl = this , _testName // Map of variation names to transclude fns , _variations = {}; var _processChoice = function (choice) { // Triggered if for some reason we get an error from sixpack, // or optionally if a user is excluded from this test via configuration if (!choice) { _setContent(_variations['default']); } else { _setContent(_variations[choice]); } } var _setContent = function (fn) { if (!fn) { return; }; fn(function(clone) { $element.replaceWith(clone); }); } // Pseudo-shim for '.keys' method // Additionally, if obj has a 'default' property, sets that as the first element // so sixpack will use it as the control var _keys = function (obj) { var keys = [] , prop; for (prop in obj) { if (!obj.hasOwnProperty(prop)) { continue; }; if (prop == 'default') { keys.unshift(prop); } else { keys.push(prop); } } return keys; } ctrl.registerSwitch = function (name) { _testName = name; sixpack.participate(_testName, _keys(_variations), _processChoice); } ctrl.registerVariation = function (variation, fn) { _variations[variation] = fn; } return ctrl; }], require: 'sixpackSwitch', link : function ($scope, $element, $attrs, ctrl) { ctrl.registerSwitch($attrs.sixpackSwitch); } } }]) // Register a variation for a test .directive('sixpackWhen', ['$log', function ($log) { return { require: '^sixpackSwitch', transclude: 'element', link: function($scope, $element, $attrs, ctrl, transcludeFn) { if ($attrs.sixpackWhen) { ctrl.registerVariation($attrs.sixpackWhen, transcludeFn); } else { $log.debug('[sixpack] When directive initialized without a name, ignoring'); } } } }]) // Register the 'default view, registered as the control variation, and // always used if sixpack errors out or if user is excluded via configuration .directive('sixpackDefault', function () { return { require: '^sixpackSwitch', transclude: 'element', link: function($scope, $element, $attrs, ctrl, transcludeFn) { ctrl.registerVariation('default', transcludeFn); } } }) .directive('sixpackConvert', ['sixpack', function (sixpack) { return { link : function ($scope, $element, $attrs) { var test = $attrs.sixpackConvert || undefined , eventType = $attrs.on || 'click'; $element.on(eventType, function () { sixpack.convert(test); }); } } }]); })(window.angular, window.sixpack);