js/VPAIDAdUnit.js
'use strict';
var IVPAIDAdUnit = require('./IVPAIDAdUnit');
var Subscriber = require('./subscriber');
var checkVPAIDInterface = IVPAIDAdUnit.checkVPAIDInterface;
var utils = require('./utils');
var METHODS = IVPAIDAdUnit.METHODS;
var ERROR = 'AdError';
var AD_CLICK = 'AdClickThru';
var FILTERED_EVENTS = IVPAIDAdUnit.EVENTS.filter(function (event) {
return event != AD_CLICK;
});
/**
* This callback is displayed as global member. The callback use nodejs error-first callback style
* @callback NodeStyleCallback
* @param {string|null}
* @param {undefined|object}
*/
/**
* VPAIDAdUnit
* @class
*
* @param VPAIDCreative
* @param {HTMLElement} [el] this will be used in initAd environmentVars.slot if defined
* @param {HTMLVideoElement} [video] this will be used in initAd environmentVars.videoSlot if defined
*/
function VPAIDAdUnit(VPAIDCreative, el, video, iframe) {
this._isValid = checkVPAIDInterface(VPAIDCreative);
if (this._isValid) {
this._creative = VPAIDCreative;
this._el = el;
this._videoEl = video;
this._iframe = iframe;
this._subscribers = new Subscriber();
utils.setFullSizeStyle(el);
$addEventsSubscribers.call(this);
}
}
VPAIDAdUnit.prototype = Object.create(IVPAIDAdUnit.prototype);
/**
* isValidVPAIDAd will return if the VPAIDCreative passed in constructor is valid or not
*
* @return {boolean}
*/
VPAIDAdUnit.prototype.isValidVPAIDAd = function isValidVPAIDAd() {
return this._isValid;
};
IVPAIDAdUnit.METHODS.forEach(function(method) {
//NOTE: this methods arguments order are implemented differently from the spec
var ignores = [
'subscribe',
'unsubscribe',
'initAd'
];
if (ignores.indexOf(method) !== -1) return;
VPAIDAdUnit.prototype[method] = function () {
var ariaty = IVPAIDAdUnit.prototype[method].length;
// TODO avoid leaking arguments
// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
var args = Array.prototype.slice.call(arguments);
var callback = (ariaty === args.length) ? args.pop() : undefined;
setTimeout(function () {
var result, error = null;
try {
result = this._creative[method].apply(this._creative, args);
} catch(e) {
error = e;
}
callOrTriggerEvent(callback, this._subscribers, error, result);
}.bind(this), 0);
};
});
/**
* initAd concreate implementation
*
* @param {number} width
* @param {number} height
* @param {string} viewMode can be 'normal', 'thumbnail' or 'fullscreen'
* @param {number} desiredBitrate indicates the desired bitrate in kbps
* @param {object} [creativeData] used for additional initialization data
* @param {object} [environmentVars] used for passing implementation-specific of js version, if el & video was used in constructor slot & videoSlot will be added to the object
* @param {NodeStyleCallback} callback
*/
VPAIDAdUnit.prototype.initAd = function initAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars, callback) {
creativeData = creativeData || {};
environmentVars = utils.extend({
slot: this._el,
videoSlot: this._videoEl
}, environmentVars || {});
setTimeout(function () {
var error;
try {
this._creative.initAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars);
} catch (e) {
error = e;
}
callOrTriggerEvent(callback, this._subscribers, error);
}.bind(this), 0);
};
/**
* subscribe
*
* @param {string} event
* @param {nodeStyleCallback} handler
* @param {object} context
*/
VPAIDAdUnit.prototype.subscribe = function subscribe(event, handler, context) {
this._subscribers.subscribe(handler, event, context);
};
/**
* unsubscribe
*
* @param {string} event
* @param {nodeStyleCallback} handler
*/
VPAIDAdUnit.prototype.unsubscribe = function unsubscribe(event, handler) {
this._subscribers.unsubscribe(handler, event);
};
//alias
VPAIDAdUnit.prototype.on = VPAIDAdUnit.prototype.subscribe;
VPAIDAdUnit.prototype.off = VPAIDAdUnit.prototype.unsubscribe;
IVPAIDAdUnit.GETTERS.forEach(function(getter) {
VPAIDAdUnit.prototype[getter] = function (callback) {
setTimeout(function () {
var result, error = null;
try {
result = this._creative[getter]();
} catch(e) {
error = e;
}
callOrTriggerEvent(callback, this._subscribers, error, result);
}.bind(this), 0);
};
});
/**
* setAdVolume
*
* @param volume
* @param {nodeStyleCallback} callback
*/
VPAIDAdUnit.prototype.setAdVolume = function setAdVolume(volume, callback) {
setTimeout(function () {
var result, error = null;
try {
this._creative.setAdVolume(volume);
result = this._creative.getAdVolume();
} catch(e) {
error = e;
}
if (!error) {
error = utils.validate(result === volume, 'failed to apply volume: ' + volume);
}
callOrTriggerEvent(callback, this._subscribers, error, result);
}.bind(this), 0);
};
VPAIDAdUnit.prototype._destroy = function destroy() {
this.stopAd();
this._subscribers.unsubscribeAll();
};
function $addEventsSubscribers() {
// some ads implement
// so they only handle one subscriber
// to handle this we create our one
FILTERED_EVENTS.forEach(function (event) {
this._creative.subscribe($trigger.bind(this, event), event);
}.bind(this));
// map the click event to be an object instead of depending of the order of the arguments
// and to be consistent with the flash
this._creative.subscribe($clickThruHook.bind(this), AD_CLICK);
// because we are adding the element inside the iframe
// the user is not able to click in the video
if (this._videoEl) {
var documentElement = this._iframe.contentDocument.documentElement;
var videoEl = this._videoEl;
documentElement.addEventListener('click', function(e) {
if (e.target === documentElement) {
videoEl.click();
}
});
}
}
function $clickThruHook(url, id, playerHandles) {
this._subscribers.triggerSync(AD_CLICK, {url: url, id: id, playerHandles: playerHandles});
}
function $trigger(event) {
// TODO avoid leaking arguments
// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
this._subscribers.trigger(event, Array.prototype.slice(arguments, 1));
}
function callOrTriggerEvent(callback, subscribers, error, result) {
if (callback) {
callback(error, result);
} else if (error) {
subscribers.trigger(ERROR, error);
}
}
module.exports = VPAIDAdUnit;