-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathab-test.js
More file actions
executable file
·163 lines (138 loc) · 4.16 KB
/
Copy pathab-test.js
File metadata and controls
executable file
·163 lines (138 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* @license
* ab-test v0.0.1
* (c) 2014 Daniel Lamb http://daniellmb.com
* License: MIT
*/
// shortcut to include both the a/b test service and directive modules
angular.module('ab.test', ['ab.test.directive']);
angular.module('ab.test.directive', ['ab.test.service', 'ngAnimate'])
// create ab test service
.factory('ab', function (abMfg) {
return abMfg();
})
// ab-test directive controller
.controller('abTestCtrl', ['$scope', 'ab', function abTestCtrl($scope, ab) {
var ctrl = this,
control = ctrl.control,
variants = ctrl.variants = [],
shown = $scope.shown || angular.noop,
select = $scope.select || angular.noop;
// register an a/b test variant
ctrl.add = function add(variant) {
// set in index on the variant
variant.index = variants.length;
variants.push(variant);
// check if variant is the a/b test "control"
if (variant.control) {
ctrl.control = control = variant;
}
};
function getResultFromSelect() {
var result = null;
var toSelect = $scope.select();
angular.forEach(variants, function(elem) {
if(elem.uid === toSelect || elem.uid === parseInt(toSelect)) {
result = elem;
}
});
return result;
}
// run the ab test
$scope.run = function run() {
// hide active variant
if (ctrl.variant) {
ctrl.variant.active = false;
}
var result= null;
// If user provides their own selection function, use that
if($scope.select) {
result = getResultFromSelect();
}
if(!result) {
result = ab.test(variants, $scope.frequency);
}
// check if there is a variant to show
if (result) {
// show variant returned
result.active = true;
ctrl.variant = result;
// notify optional callback what was shown
shown(result);
} else if (control) {
// no variant, show control instead
control.active = true;
ctrl.variant = control;
// notify optional callback what was shown
shown(control);
}
};
}])
// ab-test directive
.directive('abTest', ['abTestLink', function abTest(abTestLink) {
return {
restrict: 'EA',
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
controller: 'abTestCtrl',
scope: {
// (optional) two way binding
run: '=?abRun',
shown: '=?abShown',
// (required) two way binding
frequency: '=abFrequency',
select: '=?abSelect'
},
compile: function compile(elm, attrs) {
// a/b test frequency required
if (!angular.isDefined(attrs.abFrequency)) {
throw new Error('ab-test: ab-frequency attribute required');
}
// return post link method (see factory below)
return abTestLink;
}
};
}])
// ab-test directive link function (separate so it can be easily mocked)
.factory('abTestLink', function abTestLink() {
return function link(scope, elm, attrs, ctrl) {
var frequency = scope.frequency;
// quick data sanity checks
if (isNaN(frequency) || (frequency < 0.0001 || frequency > 1)) {
throw new Error('ab-test: test frequency must be a float between 0.0001 and 1');
}
if (ctrl.variants.length === 0) {
throw new Error('ab-test: no variants found for the a/b test');
}
// run the a/b test
scope.run();
};
})
// ab-test variant directive
.directive('abVariant', ['abVariantLink', function abVariant(abVariantLink) {
return {
require: '^abTest',
restrict: 'EA',
replace: true,
transclude: true,
template: '<div ng-transclude ng-show="active" class="ng-class:{\'ab-active\':active}; ab-animate"></div>',
scope: {
uid: '=abUid',
// (optional) two way binding
active: '=?abActive',
control: '=?abControl',
data: '=?abData'
},
// empty controller so other directives can require being 'within' an ab-variant
controller: angular.noop,
link: abVariantLink // (see factory below)
};
}])
// ab-variant directive link function (separate so it can be easily mocked)
.factory('abVariantLink', function abVariantLink() {
return function link(scope, elm, attrs, ctrl) {
// register variant with ab-test parent directive
ctrl.add(scope);
};
});