analytics/public/Analytics.js

/**
 * Module for handling all types of Analytics
 *
 * @module Analytics
 */
const Analytics = {
	types: ['adobe', 'ga'],
	modules: false,
	/*
	 * Binds initialize various types of analytics and add click listeners for click tracking
	 *
	 * @param {Array} modules - list of analytics modules to be initiated
	 * @method init
	 */
	init( modules ) {
		if ( ! modules ) {
			return;
		}

		this.modules = modules;
		[].forEach.call( Object.keys( modules ), ( key ) => {
			const analyticsModule = modules[key];
			analyticsModule.init();
		});

		// Listen to clicks to elements with data attribute trackable=true
		const trackableLinks = document.querySelectorAll( '[data-trackable="true"]' );
		for ( let i = 0; i < trackableLinks.length; i += 1 ) {
			trackableLinks[i].addEventListener( 'click', ( evt ) => {
				// click tracking enabled for adobe and ga by default
				let blockedLink = '';
				const link = evt.currentTarget.getAttribute( 'href' );
				if ( link && '_blank' !== evt.currentTarget.getAttribute( 'target' ) ) {
					evt.preventDefault();
					blockedLink = link;
				}

				let trackTypes = evt.currentTarget.dataset.tracktypes ? evt.currentTarget.dataset.tracktypes : '';
				trackTypes = '' !== trackTypes ? trackTypes.split( ',' ) : this.types;

				// Delay defaults to 0.5 seconds
				let delay = 0.5;
				if ( evt.currentTarget.dataset.trackdelay ) {
					delay = evt.currentTarget.dataset.trackdelay;
				}

				const eventType = evt.currentTarget.dataset.trackEventType || evt.type;
				const elem = evt.currentTarget;
				setTimeout( () => {
					this.track( trackTypes, this.getTrackingObject( eventType, elem ), blockedLink );
				}, parseFloat( delay, 10 ) * 1000 );
			});
		}
	},

	getTrackingObject( type, elem ) {
		let trackAction = elem.dataset.trackaction;
		trackAction = this.getTrackingValue( elem, trackAction );

		let trackData = elem.dataset.trackdata;
		trackData = trackData ? JSON.parse( this.getTrackingValue( elem, trackData ).replace( /'/g, '"' ) ) : '';

		const details = {
			eventType: type,
			action: trackAction,
			data: trackData,
			target: elem,
		};

		return details;
	},

	getTrackingValue( elem, trackingString ) {
		let updatedTrackingString = trackingString;
		const match = new RegExp( '(!)*\\${([^}]+)}', 'g' ).exec( trackingString ); // eslint-disable-line no-template-curly-in-string

		if ( match && match.length >= 3 ) {
			const [replaceString, operator, tokenKey] = match;
			let tokenValue = elem.getAttribute( tokenKey );
			if ( '!' === operator ) {
				if ( 'false' === tokenValue ) {
					tokenValue = 'true';
				} else {
					tokenValue = 'false';
				}
			}
			updatedTrackingString = updatedTrackingString.replace( replaceString, tokenValue );
		}
		return updatedTrackingString;
	},

	/*
	 * Trigger custom tracking calls
	 *
	 * examples:
	 * gn_analytics.Analytics.track( 'adobe', { action: 'next slide', data: { slidenumber: 1} } );
	 * gn_analytics.Analytics.track( 'ga', { eventType: 'Click', action: 'Next slide' } );
	 *
	 * @param {Array|String} types - types of analytics to track
	 * @param {Object} details - data to be tracked
	 * @method track
	 */
	track( types, details, blockedLink = '' ) {
		if ( ! types || ! this.modules ) {
			return;
		}

		let trackTypes = types;
		if ( 'string' === typeof ( trackTypes ) ) {
			trackTypes = [trackTypes];
		}

		const {
			eventType,
			target,
			action,
			data,
		} = details;

		[].forEach.call( trackTypes, ( type ) => {
			switch ( type ) {
			case 'adobe':
				if ( this.modules.adobe ) {
					const linkName = ( eventType ? `${eventType} | ` : '' ) + action;
					this.modules.adobe.trackLink( target, 'o', linkName, data );
				}
				break;
			case 'ga':
				/* global ga */
				if ( 'undefined' !== typeof ( ga ) ) {
					ga( 'send', 'event', eventType, action );
				}
				break;
			case 'permutive':
				if ( 'undefined' !== typeof ( window.permutive ) && data ) {
					window.permutive.track( 'AffiliateLinkClick', data );
				}
				break;
			default:
				break;
			}
		});

		// blockedLink will be handled by Adobe's tracking call
		// Handle blocked link manually for non-Adobe calls.
		if ( blockedLink && trackTypes.length < 2 && 'adobe' !== trackTypes[0]) {
			// link was blocked to ensure tracking call gets made
			// Navigate to blocked link in 500 ms
			setTimeout( () => {
				if ( blockedLink.length > 0 ) {
					window.location.href = blockedLink;
				}
			}, 500 );
		}
	},
};

export default Analytics;