'use strict'
/* global UCASUtilities, jQuery */
var googletag = googletag || {}
googletag.cmd = googletag.cmd || []
var UCASDesignFramework = UCASDesignFramework || {};
/**
* GDFP adverts.
* @namespace googleadverts
* @memberof UCASDesignFramework
* @param {object} global - UCASDesignFramework object.
* @param {object} _u - UCASUtilities object.
*/
(function (global, _u) {
global.googleadverts = {}
var slotDictionary = {}
// Define advert sizes as per DFP
var skyScraperSize = [160, 600]
var leaderboardSize = [728, 90]
var mobileBannerSize = [320, 50]
var mpuSize = [300, 250]
var verticalRectangleSize = [240, 400]
var pixel1x1Size = [1, 1]
/**
* Initialise Google DFP adverts.
* @function init
* @memberof! UCASDesignFramework.googleadverts
* @public
* @param {object} [context=document] - DOM element that we are adding adverts to.
* @example
* UCASDesignFramework.googleadverts.init()
*/
function init (context) {
context = context || document
if (!document.getElementById('google-dfp-script')) {
var script = document.createElement('script')
script.id = 'google-dfp-script'
script.src = 'https://www.googletagservices.com/tag/js/gpt.js'
script.type = 'text/javascript'
script.async = true
document.head.appendChild(script)
googletag.cmd.push(function () {
googletag.pubads().enableAsyncRendering()
googletag.pubads().enableSingleRequest()
googletag.pubads().disableInitialLoad()
googletag.enableServices()
})
}
// Insert the initial adverts currently defined in the context.
insertAdverts(context, false)
}
/**
* Refresh the adverts in a context. Performs a simple reload of each advert.
* E.g. loads a new advert for the slot should it have multiple creatives defined in DFP.
* @function refreshAdverts
* @memberof! UCASDesignFramework.googleadverts
* @public
* @param {object} [context=document] - DOM element that contains the adverts we want to refresh.
* @param {object} [ids] - list of slot placement ids to refresh adverts for.
* Optional parameter, if null then obtain placement Ids from the passed context. If the context is
* document then we don't require the Ids as we're refreshing all ads on the page. We could obviously do without
* this parameter but if we've already obtain the list of Ids, it saves querying the DOM again.
*/
function refreshAdverts (context, ids) {
context = context || document
var idsToRefresh = []
// if no Ids passed then obtain list from passed context.
if (!ids) {
googletag.cmd.push(function () {
var targetingSet = false
var placements = context.querySelectorAll('[data-google-query-id]')
_u.forEach(placements, function (i, el) {
var parent = el.parentElement
if (parent.classList.contains('google-placement-delayed')) {
return true
}
idsToRefresh[i] = el.getAttribute('id')
var keyValues = parseKVP(parent.getAttribute('data-google-target-values'))
if (jQuery !== 'undefined') {
keyValues = parseKVP(jQuery(parent).data('googleTargetValues'))
}
// Add a slot level targeting on the placement name if present in the keyValues object. This is a specific
// request from Elliott Donnelly.
if (keyValues['placement']) {
slotDictionary[idsToRefresh[i]].setTargeting('placement', keyValues['placement'])
}
// If we don't yet have targeting set, set it from the values on the first valid banner we find.
if (!targetingSet) {
targetPageLevel(keyValues)
targetingSet = true
_u.log.log('Setting page level targeting on reload')
_u.log.log(keyValues)
}
})
})
} else {
idsToRefresh = ids
}
// Find the slots to refresh from the slotDictionary, then pass slot array to refresh function.
var slotsToRefresh = []
_u.forEach(idsToRefresh, function (i, el) {
slotsToRefresh[i] = slotDictionary[el]
})
if (slotsToRefresh.length > 0) {
googletag.cmd.push(function () {
_u.log.log('Refreshing specific banners')
_u.log.log(slotsToRefresh)
googletag.pubads().refresh(slotsToRefresh)
})
}
}
/**
* Parses Key Value Pairs and outputs as true JSON if valid.
* @private
* @param {string} keyValues - data attribute of key value pairs that require parsing.
* @returns {object|string} keyValues - JSON encoded key value pairs, or orignal invalid JSON string.
*/
function parseKVP (keyValues) {
if (keyValues) {
if (typeof keyValues === 'string' || keyValues instanceof String) {
// Replace any single quotes with double quotes to make it valid JSON
keyValues = keyValues.replace(new RegExp('\'', 'g'), '"')
try {
keyValues = JSON.parse(keyValues)
} catch (e) {
// Do nothing just pass back original string.
}
}
}
return keyValues
}
/**
* Sets the page level targeting based on the KVPs passed in. Expected to have been called from within a cmd.push
* @private
* @param {object} keyValues - data attribute of key value pairs that require parsing
*/
function targetPageLevel (keyValues) {
// Add page level targetting. Each ad tag has a set of KVPs but it will only use the ones from the first tag it processess.
// Placement KVP is sent as a slot level target so is ignored in this loop.
// All ads in search will have the same set of KVPs and it seems that UCAS.com does too. Coded this way as originally planned
// to have these items as slot level targetting but Elliott requested that I change to page level. Not sure if this will be problematic in future.
// Obviously, if this function is called with a context containing a new slot that has different targetting values it will replace the existing.
// Adverts refreshed after this point will use the new page level targetting values. This could surely be inproved in a future version.
_u.forEach(Object.keys(keyValues), function (i, el) {
if (el.toLowerCase() !== 'placement') {
googletag.pubads().setTargeting(el, keyValues[el])
}
})
}
/**
* Insert new advert slots based on the context passed in.
* @function insertAdverts
* @memberof! UCASDesignFramework.googleadverts
* @public
* @param {object} [context=document] - DOM element that contains the new advert slot we want to load.
* @param {bool} [immediateLoad=true] - Bool to decide whether to immediately load an advert or wait until all are defined and then load.
* @returns {bool} - success
*/
function insertAdverts (context, immediateLoad) {
context = context || document
if (immediateLoad === undefined) {
immediateLoad = true
}
var keyValues = []
var placementIds = []
// Only obtain advert divs that have not already been loaded.
var placements = context.querySelectorAll('[data-google-slot-id]:not(.google-placement-loaded):not(.google-placement-delayed)')
if (placements.length < 1) {
return true
}
var targetingSet = false
googletag.cmd.push(function () {
for (var i = 0; i < placements.length; i++) {
var element = placements[i]
// Prevent double loading of slots. Selector above should hopefully avoid this.
// This is a fail safe.
if (element.classList.contains('google-placement-loaded')) {
return true
}
// Make sure there is nothing else in this container.
while (element.firstChild) {
element.removeChild(element.firstChild)
}
placementIds[i] = _u.unique.genId()
var slotId = element.getAttribute('data-google-slot-id')
if (jQuery !== 'undefined') {
slotId = jQuery(element).data('googleSlotId')
}
var sizeAttribute = element.getAttribute('data-google-size')
// Parse any JSON provided by the attribute.
var parsedSize = parseKVP(sizeAttribute)
// Set defaults to always show.
var customMinBreakpoint = 1 // Set to minimum possible.
var customMaxBreakpoint = 9999 // Set to some arbitrary large width.
var size
var customSize
if (parsedSize !== null && typeof parsedSize === 'object') {
size = parsedSize.size
if (typeof parsedSize.min !== 'undefined') {
customMinBreakpoint = parsedSize.min
}
if (typeof parsedSize.max !== 'undefined') {
customMaxBreakpoint = parsedSize.max
}
if (typeof parsedSize.width !== 'undefined' && typeof parsedSize.height !== 'undefined') {
customSize = [parsedSize.width, parsedSize.height]
}
} else {
size = element.getAttribute('data-google-size')
}
if (size) {
var slot
switch (size) {
case 'skyscraper':
// Skyscraper advert only to show in 1024px width or above (any height)
var desktopSkyscraperMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([UCASDesignFramework.size.breakpoints.medium, 1], skyScraperSize)
.build()
slot = googletag.defineSlot(slotId, skyScraperSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(desktopSkyscraperMapping)
break
case 'skyscraper-large':
// Skyscraper advert (used in the search results page) only to show in 1366px width or above (any height)
var largeDesktopSkyscraperMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([1366, 1], skyScraperSize)
.build()
slot = googletag.defineSlot(slotId, skyScraperSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(largeDesktopSkyscraperMapping)
break
case 'leaderboard':
// Leaderboard advert only to show in 1200px width or above (any height)
var desktopLeaderboardMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([UCASDesignFramework.size.breakpoints.large, 1], leaderboardSize)
.build()
slot = googletag.defineSlot(slotId, leaderboardSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(desktopLeaderboardMapping)
break
case 'mobile-banner':
// Mobile banner advert only to show in px width less than 721 (any height)
var mobileBannerMapping = googletag.sizeMapping()
.addSize([0, 0], mobileBannerSize)
.addSize([UCASDesignFramework.size.breakpoints.small, 1], [])
.build()
slot = googletag.defineSlot(slotId, mobileBannerSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(mobileBannerMapping)
break
case 'mpu':
// MPU advert to show in specified browser width
var mpuMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([customMinBreakpoint, 0], mpuSize) // Show mpuSize from min upwards.
.addSize([customMaxBreakpoint, 0], []) // Show nothing from max upwards.
.build()
slot = googletag.defineSlot(slotId, mpuSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(mpuMapping)
break
case 'vertical-rectangle':
// Verticle Rectangle to show in anything above 540px width
var verticalRectangleMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([541, 1], verticalRectangleSize)
.build()
slot = googletag.defineSlot(slotId, mpuSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(verticalRectangleMapping)
break
case '1x1-pixel':
// 1x1 pixel advert to show in any px width.
var pixel1x1Mapping = googletag.sizeMapping()
.addSize([0, 0], pixel1x1Size)
.build()
slot = googletag.defineSlot(slotId, pixel1x1Size, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(pixel1x1Mapping)
break
default:
// Unknown size so return false
return false
case 'custom':
// Custom advert to show in specified browser width
var customMapping = googletag.sizeMapping()
.addSize([0, 0], [])
.addSize([customMinBreakpoint, 0], customSize) // Show mpuSize from min upwards.
.addSize([customMaxBreakpoint, 0], []) // Show nothing from max upwards.
.build()
slot = googletag.defineSlot(slotId, customSize, placementIds[i]).addService(googletag.pubads())
slot.defineSizeMapping(customMapping)
break
}
keyValues = parseKVP(element.getAttribute('data-google-target-values'))
if (jQuery !== 'undefined') {
keyValues = parseKVP(jQuery(element).data('googleTargetValues'))
}
_u.log.log('Inserted banner')
_u.log.log(slotId)
// Add a slot level targeting on the placement name if present in the keyValues object. This is a specific
// request from Elliott Donnelly.
if (keyValues['placement']) {
slot.setTargeting('placement', keyValues['placement'])
}
// If we don't yet have targeting set, set it from the values on the first valid banner we find.
if (!targetingSet) {
targetPageLevel(keyValues)
targetingSet = true
_u.log.log('Setting page level targetting on insert')
_u.log.log(keyValues)
}
// Record the slot in the dictionary so that we can refresh on a per slot basis if requested.
slotDictionary[placementIds[i]] = slot
}
// If successfully loaded, add the class to indicate that we shouldn't process it again.
if (element.classList.length > 0) {
element.className += (' google-placement-loaded')
} else {
element.className = ('google-placement-loaded')
}
// Insert the Ad Div into the context.
var divId = placementIds[i]
var advertContainer = document.createElement('div')
advertContainer.id = divId
placements[i].appendChild(advertContainer)
googletag.display(divId)
// Immediately load the slot?
if (immediateLoad) {
googletag.pubads().refresh([slot])
}
}
if (!immediateLoad) {
googletag.pubads().refresh()
}
})
}
// Refresh all ads on a page, e.g. this is triggered when a user resizes the page and
// switches to a different Design Framework breakpoint. If in future we require a single, whole page advert refresh
// we could extrapolate this into a function and expose it.
document.addEventListener('dfpAdvertResize', function () {
googletag.cmd.push(function () {
googletag.pubads().refresh()
})
})
global.googleadverts = {
init: init,
insertAdverts: insertAdverts,
refreshAdverts: refreshAdverts
}
})(UCASDesignFramework, UCASUtilities)