Files
AstralRinth/modules/gpt-ads/lib/templates/component.js
Redblueflame 0bda636113 Added mobile friendly ads & ads (#161)
* Fix spacing issues on mobile

* Added back linting on modules directory.
Please don't look at the dirty fixes :x

* Add support for responsive ads.

* Add lazy loading of images.
2021-04-15 06:48:33 -07:00

261 lines
7.0 KiB
JavaScript

/* eslint-disable no-undef */
export default {
name: '<%= options.componentName %>',
data: () => ({
adSlot: null,
mapping: [],
currentSizeMappingIndex: null,
windowResizeListenerDebounce: null,
isEmpty: true,
}),
props: {
adUnit: {
type: String,
required: true,
},
size: {
type: [Array, String],
required: true,
},
sizeMapping: {
type: Array,
required: false,
default: () => [],
},
id: {
type: [Number, String],
required: false,
default: () => Math.random().toString(36).substring(5),
},
isResponsive: {
type: Boolean,
required: false,
default: '<%= options.responsive %>' === 'true',
},
windowResizeDebounce: {
type: Number,
required: false,
default: 300,
},
collapseEmptyDiv: {
type: Boolean,
required: false,
default: null,
},
},
computed: {
ghostMode() {
return this.$config.ads.ghostMode ?? '<%= options.ghostMode %>' === true
},
networkCode() {
const { $gptAds } = this
return $gptAds ? $gptAds.networkCode : null
},
adUnitPath() {
const { networkCode, adUnit } = this
return `/${networkCode}/${adUnit}`
},
divId() {
const { id } = this
return `div-gpt-ad-${id}-0`
},
formattedSize() {
return this.formatSizeList(this.size)
},
style() {
if (this.ghostMode) {
const { formattedSize, currentSizeMappingIndex, mapping } = this
let baseSize = formattedSize
if (currentSizeMappingIndex !== null) {
baseSize = mapping[currentSizeMappingIndex][1]
}
const size = Array.isArray(baseSize[0])
? baseSize[0]
: [baseSize[0], baseSize[1]]
const [width, height] = size
return {
margin: '0 auto',
width: `${width}px`,
height: `${height}px`,
border: '1px solid black',
}
}
return null
},
},
methods: {
/**
* Formats a given size to make it compatible with GPT
* If size is an Array, it is returned as is
* If size is a string, it is formatted so that 123x456 becomes [123, 456]
*
* @param {Array,string} size The size
* @return {Array} Formatted size
*/
formatSize(size) {
if (Array.isArray(size)) {
return size
}
if (typeof size === 'string') {
return size.split('x').map((value) => parseInt(value, 10))
}
return []
},
/**
* Formats a given list of sizes to make it compatible with GPT API
* If sizesList is an Array, it is returned as is
* If sizesList is a string, it is formatted so that
* 123x456,654x321 becomes [[123, 456], [654, 321]]
*
* @param {Array,string} sizesList The sizes
* @return {Array} Formatted sizes list
*/
formatSizeList(sizesList) {
if (Array.isArray(sizesList)) {
return sizesList
}
if (typeof sizesList === 'string') {
return sizesList.split(',').map((size) => this.formatSize(size))
}
return []
},
/**
* Refresh ad slot
*/
refreshSlot() {
console.log('Refreshing slot.')
googletag.pubads().refresh([this.adSlot])
},
handleSlotRenderEnded(event) {
if (event.slot.getSlotId().getDomId() !== this.divId) {
return
}
this.isEmpty = !!event.isEmpty
},
/**
* Window resize event listener
* Attached only when responsive mode is enabled, it checks wether a different size
* mapping can be activated after resize and forces the slot to be refreshed if it's
* the case
*/
handleWindowResize() {
const { windowResizeDebounce } = this
clearTimeout(this.windowResizeListenerDebounce)
this.windowResizeListenerDebounce = setTimeout(() => {
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex()
if (currentSizeMappingIndex !== this.currentSizeMappingIndex) {
if (!this.ghostMode) {
this.refreshSlot()
}
this.currentSizeMappingIndex = currentSizeMappingIndex
}
}, windowResizeDebounce)
},
/**
* Gets the current size mapping index
*
* @return {Number} The current size mapping index
*/
getCurrentSizeMappingIndex() {
const mapping = this.mapping || []
let index = null
mapping.some((size, i) => {
const [browserSize] = size
const [width, height] = browserSize
const mediaQuery = `(min-width: ${width}px) and (min-height: ${height}px)`
if (window.matchMedia(mediaQuery).matches) {
index = i
return true
}
return false
})
return index
},
},
mounted() {
if (!window.googletag) {
return
}
const {
adUnitPath,
divId,
sizeMapping,
isResponsive,
collapseEmptyDiv,
} = this
// Init Ad slot
googletag.cmd.push(() => {
const pubadsService = googletag.pubads()
pubadsService.addEventListener(
'slotRenderEnded',
this.handleSlotRenderEnded
)
pubadsService.setTargeting('path', this.$route.path)
const adSlot = googletag
.defineSlot(adUnitPath, this.formattedSize, divId)
.addService(pubadsService)
// Collapse empty div slot-level override
if (collapseEmptyDiv !== null) {
adSlot.setCollapseEmptyDiv(collapseEmptyDiv)
}
// Build size mapping if any
if (sizeMapping.length > 0) {
const mapping = googletag.sizeMapping()
sizeMapping.forEach((size) => {
const browserSize = this.formatSize(size[0])
const adSizes = this.formatSizeList(size[1])
mapping.addSize(browserSize, adSizes)
this.mapping.push([browserSize, adSizes])
})
adSlot.defineSizeMapping(mapping.build())
}
// Init responsive behavior
if (this.sizeMapping.length > 0 && isResponsive) {
const currentSizeMappingIndex = this.getCurrentSizeMappingIndex()
this.currentSizeMappingIndex = currentSizeMappingIndex
window.addEventListener('resize', this.handleWindowResize)
}
this.adSlot = adSlot
this.$gptAds.slots.push(adSlot)
if (!this.ghostMode) {
googletag.display(divId)
if (this.$gptAds.individualRefresh) {
this.refreshSlot()
}
}
})
},
beforeDestroy() {
console.log('Destroying ad.')
if (!googletag) {
return
}
// Destroy ad slot
googletag.cmd.push(() => {
googletag.destroySlots([this.adSlot])
})
// Remove window resize listener
window.removeEventListener('resize', this.handleWindowResize)
},
render(h) {
const { divId, style, isEmpty } = this
const classAttr = isEmpty ? '<%= options.emptyClass %>' : ''
return h('div', {
style,
attrs: {
id: divId,
class: classAttr,
},
domProps: { innerHTML: '' },
})
},
}