2015-10-27 06:58:27 -04:00
|
|
|
(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', ['ngCookies'])
|
|
|
|
.provider('sixpack', function() {
|
|
|
|
var $body
|
|
|
|
, _tests = []
|
|
|
|
, _choices = {}
|
|
|
|
, _opts = {
|
|
|
|
baseUrl: '',
|
|
|
|
debug: false,
|
|
|
|
}
|
|
|
|
, sp = window.sixpack;
|
|
|
|
|
|
|
|
this.setOptions = function (options) {
|
|
|
|
angular.extend(_opts, options || {});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$get = ['$cookies','$timeout', '$log', function($cookies, $timeout, $log) {
|
|
|
|
var _cookiePrefix = 'sixpack-'
|
|
|
|
, _session
|
|
|
|
, _clientId;
|
|
|
|
|
|
|
|
var _getOrInitSession = function () {
|
|
|
|
if (!_session) {
|
|
|
|
if (_clientId = $cookies[_cookiePrefix + 'clientId']) {
|
|
|
|
_session = new sp.Session({client_id:_clientId, base_url:_opts.baseUrl});
|
|
|
|
} else {
|
2015-11-17 10:54:59 -05:00
|
|
|
_session = new sp.Session({client_id:_opts.client_id, base_url:_opts.baseUrl});
|
2015-10-27 06:58:27 -04:00
|
|
|
$cookies[_cookiePrefix + 'clientId'] = _clientId = _session.client_id;
|
|
|
|
}
|
|
|
|
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)
|
2015-11-16 11:22:27 -05:00
|
|
|
convert : function (testName, kpi, callback) {
|
|
|
|
if (typeof kpi === 'function') {
|
|
|
|
callback = kpi;
|
|
|
|
kpi = null;
|
|
|
|
}
|
2015-10-27 06:58:27 -04:00
|
|
|
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 = [];
|
2015-11-16 11:22:27 -05:00
|
|
|
session.convert(test, kpi, function (err, res) {
|
2015-10-27 06:58:27 -04:00
|
|
|
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);
|
|
|
|
};
|
2015-11-16 11:22:27 -05:00
|
|
|
session.convert(testName, kpi, function (err, res) {
|
2015-10-27 06:58:27 -04:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
|