Merge pull request #5047 from colemanw/CRM-15898
[civicrm-core.git] / js / angular-crmMailingAB / directives.js
CommitLineData
22bc3e48 1(function (angular, $, _) {
22bc3e48
TO
2
3 // example:
4 // scope.myAbtest = new CrmMailingAB();
5 // <crm-mailing-ab-block-mailing="{fromAddressA: 1, fromAddressB: 1}" crm-abtest="myAbtest" />
1d4d0279 6 var simpleDirectives = {
ef5d18a1
TO
7 crmMailingAbBlockMailing: '~/crmMailingAB/joint-mailing.html',
8 crmMailingAbBlockSetup: '~/crmMailingAB/setup.html'
1d4d0279
TO
9 };
10 _.each(simpleDirectives, function (templateUrl, directiveName) {
efd95528 11 angular.module('crmMailingAB').directive(directiveName, function ($parse, crmMailingABCriteria) {
1d4d0279
TO
12 var scopeDesc = {crmAbtest: '@'};
13 scopeDesc[directiveName] = '@';
14
15 return {
16 scope: scopeDesc,
17 templateUrl: templateUrl,
18 link: function (scope, elm, attr) {
19 var model = $parse(attr.crmAbtest);
20 scope.abtest = model(scope.$parent);
21 scope.crmMailingConst = CRM.crmMailing;
22 scope.crmMailingABCriteria = crmMailingABCriteria;
5d8901af 23 scope.ts = CRM.ts(null);
22bc3e48 24
1d4d0279
TO
25 var fieldsModel = $parse(attr[directiveName]);
26 scope.fields = fieldsModel(scope.$parent);
27 }
28 };
29 });
cdb373b4
TO
30 });
31
32 // example: <div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div>
efd95528 33 angular.module('crmMailingAB').directive('crmMailingAbSlider', function () {
cdb373b4
TO
34 return {
35 require: '?ngModel',
36 scope: {},
ef5d18a1 37 templateUrl: '~/crmMailingAB/slider.html',
cdb373b4
TO
38 link: function (scope, element, attrs, ngModel) {
39 var TEST_MIN = 1, TEST_MAX = 50;
40 var sliders = $('.slider-test,.slider-win', element);
41 var sliderTests = $('.slider-test', element);
42 var sliderWin = $('.slider-win', element);
43
5d8901af 44 scope.ts = CRM.ts(null);
cdb373b4
TO
45 scope.testValue = 0;
46 scope.winValue = 100;
47
48 // set the base value (following a GUI event)
49 function setValue(value) {
50 value = Math.min(TEST_MAX, Math.max(TEST_MIN, value));
51 scope.$apply(function () {
52 ngModel.$setViewValue(value);
53 scope.testValue = value;
54 scope.winValue = 100 - (2 * scope.testValue);
55 sliderTests.slider('value', scope.testValue);
56 sliderWin.slider('value', scope.winValue);
57 });
58 }
59
60 sliders.slider({
61 min: 0,
62 max: 100,
63 range: 'min',
64 step: 1
65 });
66 sliderTests.slider({
67 slide: function slideTest(event, ui) {
68 event.preventDefault();
69 setValue(ui.value);
70 }
71 });
72 sliderWin.slider({
73 slide: function slideWinner(event, ui) {
74 event.preventDefault();
75 setValue(Math.round((100 - ui.value) / 2));
76 }
77 });
78
79 ngModel.$render = function () {
80 scope.testValue = ngModel.$viewValue;
81 scope.winValue = 100 - (2 * scope.testValue);
82 sliderTests.slider('value', scope.testValue);
83 sliderWin.slider('value', scope.winValue);
84 };
85 }
86 };
87 });
360aaa75
TO
88
89 // FIXME: This code is long and hasn't been fully working for me, but I've moved it into a spot
90 // where it at least fits in a bit better.
91
92 // example: <div crm-mailing-ab-stats="{split_count: 6, criteria:'Open'}" crm-abtest="myabtest" />
93 // options (see also: Mailing.graph_stats API)
94 // - split_count: int
95 // - criteria: string
96 // - target_date: string, date
97 // - target_url: string
98 angular.module('crmMailingAB').directive('crmMailingAbStats', function (crmApi, $parse, crmNow) {
99 return {
100 scope: {
101 crmMailingAbStats: '@',
102 crmAbtest: '@'
103 },
104 template: '<div class="crm-mailing-ab-stats"></div>',
105 link: function (scope, element, attrs) {
106 var abtestModel = $parse(attrs.crmAbtest);
107 var optionModel = $parse(attrs.crmMailingAbStats);
108 var options = angular.extend({}, optionModel(scope.$parent), {
109 criteria: 'Open', // e.g. 'Open', 'Total Unique Clicks'
110 split_count: 5
111 });
112
113 scope.$watch(attrs.crmAbtest, refresh);
114 function refresh() {
115 var now = crmNow();
116 var abtest = abtestModel(scope.$parent);
117 if (!abtest) {
118 console.log('failed to draw stats - missing abtest');
119 return;
120 }
121
122 scope.graph_data = [
123 {},
124 {},
125 {},
126 {},
127 {}
128 ];
129 var keep_cnt = 0;
130
131 for (var i = 1; i <= options.split_count; i++) {
132 var result = crmApi('MailingAB', 'graph_stats', {
133 id: abtest.ab.id,
134 target_date: abtest.ab.declare_winning_time ? abtest.ab.declare_winning_time : now,
135 target_url: null, // FIXME
136 criteria: options.criteria,
137 split_count: options.split_count,
138 split_count_select: i
139 });
175762a3 140 /*jshint -W083 */
360aaa75
TO
141 result.then(function (data) {
142 var temp = 0;
143 keep_cnt++;
144 for (var key in data.values.A) {
145 temp = key;
146 }
147 var t = data.values.A[temp].time.split(" ");
148 var m = t[0];
149 var year = t[2];
150 var day = t[1].substr(0, t[1].length - 3);
f2bad133 151 var t1, hur, hour, min;
175762a3 152 if (_.isEmpty(t[3])) {
f2bad133
TO
153 t1 = t[4].split(":");
154 hur = t1[0];
360aaa75
TO
155 if (t[5] == "AM") {
156 hour = hur;
157 if (hour == 12) {
158 hour = 0;
159 }
160 }
161 if (t[5] == "PM") {
162 hour = parseInt(hur) + 12;
163 }
f2bad133 164 min = t1[1];
360aaa75
TO
165 }
166 else {
f2bad133
TO
167 t1 = t[3].split(":");
168 hur = t1[0];
360aaa75
TO
169 if (t[4] == "AM") {
170 hour = hur;
171 if (hour == 12) {
172 hour = 0;
173 }
174 }
175 if (t[4] == "PM") {
176 hour = parseInt(hur) + 12;
177 }
f2bad133 178 min = t1[1];
360aaa75
TO
179 }
180 var month = 0;
181 switch (m) {
182 case "January":
183 month = 0;
184 break;
185 case "February":
186 month = 1;
187 break;
188 case "March":
189 month = 2;
190 break;
191 case "April":
192 month = 3;
193 break;
194 case "May":
195 month = 4;
196 break;
197 case "June":
198 month = 5;
199 break;
200 case "July":
201 month = 6;
202 break;
203 case "August":
204 month = 7;
205 break;
206 case "September":
207 month = 8;
208 break;
209 case "October":
210 month = 9;
211 break;
212 case "November":
213 month = 10;
214 break;
215 case "December":
216 month = 11;
217 break;
218
219 }
220 var tp = new Date(year, month, day, hour, min, 0, 0);
221 scope.graph_data[temp - 1] = {
222 time: tp,
223 x: data.values.A[temp].count,
224 y: data.values.B[temp].count
225 };
226
227 if (keep_cnt == options.split_count) {
228 scope.graphload = true;
f2bad133 229 data = scope.graph_data;
360aaa75
TO
230
231 // set up a colour variable
232 var color = d3.scale.category10();
233
234 // map one colour each to x, y and z
235 // keys grabs the key value or heading of each key value pair in the json
236 // but not time
237 color.domain(d3.keys(data[0]).filter(function (key) {
238 return key !== "time";
239 }));
240
241 // create a nested series for passing to the line generator
242 // it's best understood by console logging the data
243 var series = color.domain().map(function (name) {
244 return {
245 name: name,
246 values: data.map(function (d) {
247 return {
248 time: d.time,
249 score: +d[name]
250 };
251 })
252 };
253 });
254
255 // Set the dimensions of the canvas / graph
256 var margin = {
257 top: 30,
258 right: 20,
259 bottom: 40,
260 left: 75
261 },
262 width = 550 - margin.left - margin.right,
263 height = 350 - margin.top - margin.bottom;
264
265 // Set the ranges
266 //var x = d3.time.scale().range([0, width]).domain([0,10]);
267 var x = d3.time.scale().range([0, width]);
268 var y = d3.scale.linear().range([height, 0]);
269
270 // Define the axes
271 var xAxis = d3.svg.axis().scale(x)
272 .orient("bottom").ticks(10);
273
274 var yAxis = d3.svg.axis().scale(y)
275 .orient("left").ticks(5);
276
277 // Define the line
278 // Note you plot the time / score pair from each key you created ealier
279 var valueline = d3.svg.line()
280 .x(function (d) {
281 return x(d.time);
282 })
283 .y(function (d) {
284 return y(d.score);
285 });
286
287 // Adds the svg canvas
288 var svg = d3.select($('.crm-mailing-ab-stats', element)[0])
289 .append("svg")
290 .attr("width", width + margin.left + margin.right)
291 .attr("height", height + margin.top + margin.bottom)
292 .append("g")
293 .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
294
295 // Scale the range of the data
296 x.domain(d3.extent(data, function (d) {
297 return d.time;
298 }));
299
300 // note the nested nature of this you need to dig an additional level
301 y.domain([
302 d3.min(series, function (c) {
303 return d3.min(c.values, function (v) {
304 return v.score;
305 });
306 }),
307 d3.max(series, function (c) {
308 return d3.max(c.values, function (v) {
309 return v.score;
310 });
311 })
312 ]);
313 svg.append("text") // text label for the x axis
314 .attr("x", width / 2)
315 .attr("y", height + margin.bottom)
316 .style("text-anchor", "middle")
317 .text("Time");
318
319 svg.append("text") // text label for the x axis
320 .style("text-anchor", "middle")
321 .text(scope.winnercriteria).attr("transform",function (d) {
f2bad133 322 return "rotate(-90)";
360aaa75
TO
323 }).attr("x", -height / 2)
324 .attr("y", -30);
325
326 // create a variable called series and bind the date
327 // for each series append a g element and class it as series for css styling
f2bad133 328 series = svg.selectAll(".series")
360aaa75
TO
329 .data(series)
330 .enter().append("g")
331 .attr("class", "series");
332
333 // create the path for each series in the variable series i.e. x, y and z
334 // pass each object called x, y nad z to the lne generator
335 series.append("path")
336 .attr("class", "line")
337 .attr("d", function (d) {
338 // console.log(d); // to see how d3 iterates through series
339 return valueline(d.values);
340 })
341 .style("stroke", function (d) {
342 return color(d.name);
343 });
344
345 // Add the X Axis
346 svg.append("g") // Add the X Axis
347 .attr("class", "x axis")
348 .attr("transform", "translate(0," + height + ")")
349 .call(xAxis)
350 .selectAll("text")
351 .attr("transform", function (d) {
352 return "rotate(-30)";
353 });
354
355 // Add the Y Axis
356 svg.append("g") // Add the Y Axis
357 .attr("class", "y axis")
358 .call(yAxis);
359 }
360 });
361 }
362 }
363 } // link()
364 };
365 });
22bc3e48 366})(angular, CRM.$, CRM._);