-
Notifications
You must be signed in to change notification settings - Fork 39
/
angular-dfp.js
470 lines (393 loc) · 12.3 KB
/
angular-dfp.js
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
'use strict';
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
angular.module('ngDfp', [])
.constant('ngDfpUrl', '//www.googletagservices.com/tag/js/gpt.js')
.provider('DoubleClick', ['ngDfpUrl', function (ngDfpUrl) {
/**
Holds slot configurations.
*/
var slots = {};
/**
Defined Slots, so we can refresh the ads
*/
var definedSlots = {};
/**
Holds size mapping configuration
*/
var sizeMapping = {};
/**
If configured, all ads will be refreshed at the same interval
*/
var refreshInterval = null;
/**
If false the google ads library won't be loaded and no promises will be fulfilled.
*/
var enabled = true;
/**
Defined Page targeting key->values
*/
var pageTargeting = {};
/**
Collapse empty divs if true
*/
var collapseEmptyDivs = false;
/**
Center divs if true
*/
var setCentering = false;
/**
* If true, enables Single Request Architecture (SRA)
* @type {boolean}
*/
var enableSingleRequest = true;
/**
This initializes the dfp script in the document. Loosely based on angular-re-captcha's
method of loading the script with promises.
@link https://github.com/mllrsohn/angular-re-captcha/blob/master/angular-re-captcha.js
*/
this._createTag = function (callback) {
if ( ! enabled) {
return;
}
var gads = document.createElement('script'),
useSSL = 'https:' === document.location.protocol,
node = document.getElementsByTagName('script')[0];
gads.async = true;
gads.type = 'text/javascript';
gads.src = (useSSL ? 'https:' : 'http:') + ngDfpUrl;
// Insert before any JS include.
node.parentNode.insertBefore(gads, node);
gads.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
};
gads.onload = callback;
};
/**
Initializes and configures the slots that were added with defineSlot.
*/
this._initialize = function () {
var self = this;
// when the GPT JavaScript is loaded, it looks through the array and executes all the functions in order
googletag.cmd.push(function() {
angular.forEach(slots, function (slot, id) {
definedSlots[id] = googletag.defineSlot.apply(null, slot).addService(googletag.pubads());
if(sizeMapping[id]){
definedSlots[id].defineSizeMapping(sizeMapping[id]);
}
/**
If sent, set the slot specific targeting
*/
var slotTargeting = slot.getSlotTargeting();
if(slotTargeting){
angular.forEach(slotTargeting, function (value, key) {
definedSlots[id].setTargeting(value.id, value.value);
});
}
});
/**
Set the page targeting key->values
*/
angular.forEach(pageTargeting, function (value, key) {
googletag.pubads().setTargeting(key, value);
});
/**
If requested set to true the collapseEmptyDivs
*/
if (collapseEmptyDivs) {
googletag.pubads().collapseEmptyDivs();
}
/**
If requested set to true the setCentering
*/
if (setCentering) {
googletag.pubads().setCentering(true);
}
if (enableSingleRequest) {
googletag.pubads().enableSingleRequest();
}
googletag.enableServices();
googletag.pubads().addEventListener('slotRenderEnded', self._slotRenderEnded);
});
};
this._slotRenderEnded = function (event) {
var callback = slots[event.slot.getSlotId().getDomId()].renderCallback;
if (typeof callback === 'function') {
callback(event);
}
};
/**
Returns the global refresh interval
*/
this._refreshInterval = function () {
return refreshInterval;
};
/**
Allows defining the global refresh interval
*/
this.setRefreshInterval = function (interval) {
refreshInterval = interval;
// Chaining
return this;
};
/**
Stores a slot definition.
*/
this.defineSlot = function () {
var slot = arguments;
slot.getSize = function () {
return this[1];
};
/**
To be able to get the array of slot targeting key/value
Example of the json format of the arguments: [{"id":"age","value":"20-30"}]
For multiple targeting key,values example: [{"id":"age","value":"20-30"},{"id":"gender","value":"male"}]
*/
slot.getSlotTargeting = function() {
/**
The third parameter is optional
*/
if (this[3]) {
return this[3];
} else {
return false;
}
};
slot.setRenderCallback = function (callback) {
this.renderCallback = callback;
};
slots[arguments[2]] = slot;
// Chaining.
return this;
};
/**
Stores a slot size mapping.
*/
this.defineSizeMapping = function (){
var id = arguments[0];
if(!sizeMapping[id]){
sizeMapping[id] = [];
}
// Add a new size mapping ( [browser size], [slot size])
this.addSize = function() {
sizeMapping[id].push([arguments[0], arguments[1]]);
return this;
}
// Chaining.
return this;
};
/**
Enables/Disables the entire library. Basically doesn't load the google ads library.
Useful to disable ads entirely given a certain condition is met.
*/
this.setEnabled = function (setting) {
enabled = setting;
};
/**
Stores page targeting key->values
*/
this.setPageTargeting = function (key, value) {
pageTargeting[key] = value;
};
/**
Set to true the collapseEmptyDivs
*/
this.collapseEmptyDivs = function () {
collapseEmptyDivs = true;
};
/**
Set to true the setCentering
*/
this.setCentering = function (bool) {
setCentering = bool;
};
/**
* Set Single Request Architecture
* @param isEnable
*/
this.setSingleRequest = function (isEnable) {
enableSingleRequest = isEnable;
};
// Public factory API.
var self = this;
this.$get = ['$q', '$window', '$interval', function ($q, $window, $interval) {
// Neat trick from github.com/mllrsohn/angular-re-captcha
var deferred = $q.defer();
self._createTag(function () {
try {
self._initialize();
if (self._refreshInterval() !== null) {
$interval(function () {
googletag.cmd.push(function() {
$window.googletag.pubads().refresh();
});
}, self._refreshInterval());
}
deferred.resolve();
} catch (err) {
deferred.reject(err);
}
});
return {
/**
More than just getting the ad size, this
allows us to wait for the JS file to finish downloading and
configuring ads
@deprecated Use getSlot().getSize() instead.
*/
getAdSize: function (id) {
return deferred.promise.then(function () {
// Return the size of the ad. The directive should construct
// the tag by itself.
var slot = slots[id];
if (angular.isUndefined(slot)) {
throw 'Slot ' + id + ' has not been defined. Define it using DoubleClickProvider.defineSlot().';
}
return slots[id][1];
});
},
getSlot: function (id) {
return deferred.promise.then(function () {
// Return the size of the ad. The directive should construct
// the tag by itself.
var slot = slots[id];
if (angular.isUndefined(slot)) {
throw 'Slot ' + id + ' has not been defined. Define it using DoubleClickProvider.defineSlot().';
}
return slots[id];
});
},
runAd: function (id) {
googletag.cmd.push(function() {
$window.googletag.display(id);
});
},
/**
Refreshes an ad by its id or ids.
Example:
refreshAds('div-123123123-2')
refreshAds('div-123123123-2', 'div-123123123-3')
*/
refreshAds: function () {
var slots = [];
angular.forEach(arguments, function (id) {
slots.push(definedSlots[id]);
});
googletag.cmd.push(function() {
$window.googletag.pubads().refresh(slots);
});
}
};
}];
}])
.directive('ngDfpAdContainer', function () {
return {
restrict: 'A',
controller: ['$element', function ($element) {
function hide(mode) {
if (mode === 'visibility') {
$element.css('visibility', 'hidden');
}
else {
$element.hide();
}
}
function show(mode) {
if (mode === 'visibility') {
$element.css('visibility', 'visible');
}
else {
$element.show();
}
}
this.$$setVisible = function (visible, mode) {
if (visible) {
show(mode);
}
else {
hide(mode);
}
};
}]
};
})
.directive('ngDfpAd', ['$timeout', '$parse', '$interval', 'DoubleClick', function ($timeout, $parse, $interval, DoubleClick) {
return {
restrict: 'A',
template: '<div id="{{adId}}"></div>',
require: '?^ngDfpAdContainer',
scope: {
adId: '@ngDfpAd',
refresh: '@ngDfpAdRefresh',
interval: '@ngDfpAdRefreshInterval',
timeout: '@ngDfpAdRefreshTimeout'
},
replace: true,
link: function (scope, element, attrs, ngDfpAdContainer) {
scope.$watch('adId', function (id) {
// Get rid of the previous ad.
element.html('');
var intervalPromise = null;
var timeoutPromise = null;
DoubleClick.getSlot(id).then(function (slot) {
var size = slot.getSize();
element.css('width', size[0]).css('height', size[1]);
$timeout(function () {
DoubleClick.runAd(id);
});
// Only if we have a container we hide this thing
if (ngDfpAdContainer) {
slot.setRenderCallback(function () {
if (angular.isDefined(attrs.ngDfpAdHideWhenEmpty)) {
if (element.find('iframe:not([id*=hidden])')
.map(function () { return this.contentWindow.document; })
.find("body")
.children().length === 0) {
// Hide it
ngDfpAdContainer.$$setVisible(false, attrs.ngDfpAdHideWhenEmpty);
}
else {
ngDfpAdContainer.$$setVisible(true, attrs.ngDfpAdHideWhenEmpty);
}
}
});
}
// Forces Refresh
scope.$watch('refresh', function (refresh) {
if (angular.isUndefined(refresh)) {
return;
}
DoubleClick.refreshAds(id);
});
// Refresh intervals
scope.$watch('interval', function (interval) {
if (angular.isUndefined(interval)) {
return;
}
intervalPromise = $interval(function () {
DoubleClick.refreshAds(id);
}, scope.interval);
});
// Refresh after timeout
scope.$watch('timeout', function (timeout) {
if (angular.isUndefined(timeout)) {
return;
}
timeoutPromise = $timeout(function () {
DoubleClick.refreshAds(id);
}, scope.timeout);
});
// Cancel $interval and $timeout service when DOM destroy
scope.$on('$destroy', function() {
$interval.cancel(intervalPromise);
$timeout.cancel(timeoutPromise);
intervalPromise = null;
timeoutPromise = null;
});
});
});
}
};
}]);