diff --git a/riveted.js b/riveted.js index 893b555..7397d15 100644 --- a/riveted.js +++ b/riveted.js @@ -1,41 +1,108 @@ /*! - * riveted.js | v0.1 - * Copyright (c) 2014 Rob Flaherty (@robflaherty) - * Licensed under the MIT and GPL licenses. + * @preserve + * riveted.js | v0.6.2 + * Copyright (c) 2016 Rob Flaherty (@robflaherty) + * Licensed under the MIT license */ -;(function ($,window,document,undefined) { - - var defaults = { - elements: [], - minHeight: 0, - percentage: true, - testing: false - }, +/* Universal module definition */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD + define([], factory); + } else if (typeof module === 'object' && module.exports) { + // CommonJS + module.exports = factory(); + } else { + // Browser global + root.riveted = factory(); + } +}(this, function () { + +/* Riveted */ + +var riveted = (function() { + + var started = false, + stopped = false, + turnedOff = false, + clockTime = 0, + startTime = new Date(), + clockTimer = null, + idleTimer = null, + sendEvent, + sendUserTiming, + reportInterval, + idleTimeout, + nonInteraction, + universalGA, + classicGA, + universalSendCommand, + googleTagManager, + gtagFunc = false, + gaGlobal; + + function init(options) { + + // Set up options and defaults + options = options || {}; + reportInterval = parseInt(options.reportInterval, 10) || 5; + idleTimeout = parseInt(options.idleTimeout, 10) || 30; + gaGlobal = options.gaGlobal || 'ga'; + + /* + * Determine which version of GA is being used + * "ga", "_gaq", and "dataLayer" are the possible globals + */ + + if (typeof window[gaGlobal] === "function") { + universalGA = true; + } - $window = $(window), - - cache = []; + if (typeof _gaq !== "undefined" && typeof _gaq.push === "function") { + classicGA = true; + } - /* - * Plugin - */ + if (typeof dataLayer !== "undefined" && typeof dataLayer.push === "function") { + googleTagManager = true; + } + + if(typeof gtag === 'function'){ + gtagFunc = true; + } - $.riveted = function(options) { - - var active = false; + if ('gaTracker' in options && typeof options.gaTracker === 'string') { + universalSendCommand = options.gaTracker + '.send'; + } else { + universalSendCommand = 'send'; + } + + if (typeof options.eventHandler == 'function') { + sendEvent = options.eventHandler; + } - options = $.extend({}, defaults, options); + if (typeof options.userTimingHandler == 'function') { + sendUserTiming = options.userTimingHandler; + } - var setIdle; + if ('nonInteraction' in options && (options.nonInteraction === false || options.nonInteraction === 'false')) { + nonInteraction = false; + } else { + nonInteraction = true; + } - var startCount = 0; + // Basic activity event listeners + addListener(document, 'keydown', trigger); + addListener(document, 'click', trigger); + addListener(window, 'mousemove', throttle(trigger, 500)); + addListener(window, 'scroll', throttle(trigger, 500)); - var started = false; + // Page visibility listeners + addListener(document, 'visibilitychange', visibilityChange); + addListener(document, 'webkitvisibilitychange', visibilityChange); + } - /* - * Functions - */ /* * Throttle function borrowed from: @@ -70,92 +137,180 @@ } return result; }; - } + } + + /* + * Cross-browser event listening + */ - function sendEvent(action, label) { + function addListener(element, eventName, handler) { + if (element.addEventListener) { + element.addEventListener(eventName, handler, false); + } + else if (element.attachEvent) { + element.attachEvent('on' + eventName, handler); + } + else { + element['on' + eventName] = handler; + } + } - console.log('Ping'); + /* + * Function for logging User Timing event on initial interaction + */ - if (!options.testing) { + sendUserTiming = function (timingValue) { - if (typeof(ga) !== "undefined") { - //ga('send', 'event', 'Riveted', action, label, 1); - } + if (googleTagManager) { + if (gtagFunc){ - if (typeof(_gaq) !== "undefined") { - //_gaq.push(['_trackEvent', 'Riveted', action, label, 1]); - } + gtag('event', 'RivetedTiming', {'event_category':'Riveted', 'event_label': 'First Interaction', 'value': timingValue}); - if (typeof(dataLayer) !== "undefined") { - //dataLayer.push({'event':'Riveted', 'eventCategory':'Riveted', 'eventAction': action, 'eventLabel': label, 'eventValue': 1}); + } else { + + dataLayer.push({'event':'RivetedTiming', 'eventCategory':'Riveted', 'timingVar': 'First Interaction', 'timingValue': timingValue}); + } } else { - console.log('action: ' + action + '; label: ' + label); + if (universalGA) { + window[gaGlobal](universalSendCommand, 'timing', 'Riveted', 'First Interaction', timingValue); + } + + if (classicGA) { + _gaq.push(['_trackTiming', 'Riveted', 'First Interaction', timingValue, null, 100]); + } } - } - function checkIdle() { - active = false; - console.log('Setting to false'); - } + }; + /* + * Function for logging ping events + */ + sendEvent = function (time) { - function startRiveted(diff) { - - started = true; + if (googleTagManager) { + if (gtagFunc){ - setIdle = setTimeout(checkIdle, 3000); + gtag('event','Riveted', {'event_category':'Riveted', 'event_action': 'Time Spent', 'event_label': time, 'value': reportInterval, 'non_interaction': nonInteraction}); - var pingCheck = setInterval(function() { - if (active) { - sendEvent('Ping', currentCount); } else { - console.log('Idle'); + + dataLayer.push({'event':'Riveted', 'eventCategory':'Riveted', 'eventAction': 'Time Spent', 'eventLabel': time, 'eventValue': reportInterval, 'eventNonInteraction': nonInteraction}); + } - }, 1000); + } else { + if (universalGA) { + window[gaGlobal](universalSendCommand, 'event', 'Riveted', 'Time Spent', time.toString(), reportInterval, {'nonInteraction': nonInteraction}); + } + if (classicGA) { + _gaq.push(['_trackEvent', 'Riveted', 'Time Spent', time.toString(), reportInterval, nonInteraction]); + } + } + + }; + + function setIdle() { + clearTimeout(idleTimer); + stopClock(); } -/* - * Time lapsed until engaged could be an interesting metric. - * Send a user timing event on the first ping? I think so. - */ + function visibilityChange() { + if (document.hidden || document.webkitHidden) { + setIdle(); + } + } + + function clock() { + clockTime += 1; + if (clockTime > 0 && (clockTime % reportInterval === 0)) { + sendEvent(clockTime); + } + + } + + function stopClock() { + stopped = true; + clearInterval(clockTimer); + } + + function turnOff() { + setIdle(); + turnedOff = true; + } - function resetActive() { + function turnOn() { + turnedOff = false; + } - console.log('reset'); + function restartClock() { + stopped = false; + clearInterval(clockTimer); + clockTimer = setInterval(clock, 1000); + } + function startRiveted() { + + // Calculate seconds from start to first interaction var currentTime = new Date(); - var diff = Math.floor((currentTime - startTime)/1000); + var diff = currentTime - startTime; + + // Set global + started = true; + + // Send User Timing Event + sendUserTiming(diff); + + // Start clock + clockTimer = setInterval(clock, 1000); - if (!started) { - startRiveted(diff); - } else { - active = true; - clearTimeout(setIdle); - setIdle = setTimeout(checkIdle, 3000); - } } - function init() { + function resetRiveted() { + startTime = new Date(); + clockTime = 0; + started = false; + stopped = false; + clearInterval(clockTimer); + clearTimeout(idleTimer); + } - var startTime = Date.now(); + function trigger() { + + if (turnedOff) { + return; + } - $(document).on('keypress click', resetActive); - $(window).on('scroll', throttle(resetActive, 500)); + if (!started) { + startRiveted(); + } + + if (stopped) { + restartClock(); + } + clearTimeout(idleTimer); + idleTimer = setTimeout(setIdle, idleTimeout * 1000 + 100); } - init(); + return { + init: init, + trigger: trigger, + setIdle: setIdle, + on: turnOn, + off: turnOff, + reset: resetRiveted + }; + })(); - }; + return riveted; -})(jQuery,window,document); \ No newline at end of file +})); diff --git a/riveted.min.js b/riveted.min.js new file mode 100644 index 0000000..b034a41 --- /dev/null +++ b/riveted.min.js @@ -0,0 +1,7 @@ +/*! + * @preserve + * riveted.js | v0.6.2 + * Copyright (c) 2016 Rob Flaherty (@robflaherty) + * Licensed under the MIT license + */ +!function(root,factory){"function"==typeof define&&define.amd?define([],factory):"object"==typeof module&&module.exports?module.exports=factory():root.riveted=factory()}(this,(function(){var riveted;return function(){var started=!1,stopped=!1,turnedOff=!1,clockTime=0,startTime=new Date,clockTimer=null,idleTimer=null,sendEvent,sendUserTiming,reportInterval,idleTimeout,nonInteraction,universalGA,classicGA,universalSendCommand,googleTagManager,gtagFunc=!1,gaGlobal;function init(options){options=options||{},reportInterval=parseInt(options.reportInterval,10)||5,idleTimeout=parseInt(options.idleTimeout,10)||30,gaGlobal=options.gaGlobal||"ga","function"==typeof window[gaGlobal]&&(universalGA=!0),"undefined"!=typeof _gaq&&"function"==typeof _gaq.push&&(classicGA=!0),"undefined"!=typeof dataLayer&&"function"==typeof dataLayer.push&&(googleTagManager=!0),"function"==typeof gtag&&(gtagFunc=!0),universalSendCommand="gaTracker"in options&&"string"==typeof options.gaTracker?options.gaTracker+".send":"send","function"==typeof options.eventHandler&&(sendEvent=options.eventHandler),"function"==typeof options.userTimingHandler&&(sendUserTiming=options.userTimingHandler),nonInteraction=!("nonInteraction"in options)||!1!==options.nonInteraction&&"false"!==options.nonInteraction,addListener(document,"keydown",trigger),addListener(document,"click",trigger),addListener(window,"mousemove",throttle(trigger,500)),addListener(window,"scroll",throttle(trigger,500)),addListener(document,"visibilitychange",visibilityChange),addListener(document,"webkitvisibilitychange",visibilityChange)}function throttle(func,wait){var context,args,result,timeout=null,previous=0,later=function(){previous=new Date,timeout=null,result=func.apply(context,args)};return function(){var now=new Date;previous||(previous=now);var remaining=wait-(now-previous);return context=this,args=arguments,remaining<=0?(clearTimeout(timeout),timeout=null,previous=now,result=func.apply(context,args)):timeout||(timeout=setTimeout(later,remaining)),result}}function addListener(element,eventName,handler){element.addEventListener?element.addEventListener(eventName,handler,!1):element.attachEvent?element.attachEvent("on"+eventName,handler):element["on"+eventName]=handler}function setIdle(){clearTimeout(idleTimer),stopClock()}function visibilityChange(){(document.hidden||document.webkitHidden)&&setIdle()}function clock(){(clockTime+=1)>0&&clockTime%reportInterval==0&&sendEvent(clockTime)}function stopClock(){stopped=!0,clearInterval(clockTimer)}function turnOff(){setIdle(),turnedOff=!0}function turnOn(){turnedOff=!1}function restartClock(){stopped=!1,clearInterval(clockTimer),clockTimer=setInterval(clock,1e3)}function startRiveted(){var currentTime=new Date,diff;started=!0,sendUserTiming(currentTime-startTime),clockTimer=setInterval(clock,1e3)}function resetRiveted(){startTime=new Date,clockTime=0,started=!1,stopped=!1,clearInterval(clockTimer),clearTimeout(idleTimer)}function trigger(){turnedOff||(started||startRiveted(),stopped&&restartClock(),clearTimeout(idleTimer),idleTimer=setTimeout(setIdle,1e3*idleTimeout+100))}return sendUserTiming=function(timingValue){googleTagManager?gtagFunc?gtag("event","RivetedTiming",{event_category:"Riveted",event_label:"First Interaction",value:timingValue}):dataLayer.push({event:"RivetedTiming",eventCategory:"Riveted",timingVar:"First Interaction",timingValue:timingValue}):(universalGA&&window[gaGlobal](universalSendCommand,"timing","Riveted","First Interaction",timingValue),classicGA&&_gaq.push(["_trackTiming","Riveted","First Interaction",timingValue,null,100]))},sendEvent=function(time){googleTagManager?gtagFunc?gtag("event","Riveted",{event_category:"Riveted",event_action:"Time Spent",event_label:time,value:reportInterval,non_interaction:nonInteraction}):dataLayer.push({event:"Riveted",eventCategory:"Riveted",eventAction:"Time Spent",eventLabel:time,eventValue:reportInterval,eventNonInteraction:nonInteraction}):(universalGA&&window[gaGlobal](universalSendCommand,"event","Riveted","Time Spent",time.toString(),reportInterval,{nonInteraction:nonInteraction}),classicGA&&_gaq.push(["_trackEvent","Riveted","Time Spent",time.toString(),reportInterval,nonInteraction]))},{init:init,trigger:trigger,setIdle:setIdle,on:turnOn,off:turnOff,reset:resetRiveted}}()})); \ No newline at end of file