src/engagement.js
/**
* The engagement.js module includes functions that track events related to a visitor's behavior
* and level of engagement. How are visitors engaging with a page? Are they reading the content?
* Commenting? Filling out forms? Or are they getting stuck on forms? Do they abandom them?
* These kind of questions are answered by the events this module sends to GA.
*
*/
module.exports = {
/**
* Tracks a "read" event when users have spent enough time on a page and scrolled far enough.
*
* @param {Object} opts
* minTime: The amount of time, in seconds, that must pass before the event is considered valid (estimated time to read the content?).
* selector: The element selector (class, id, etc.) that is measured for scrolling.
* category: The Google Analytics Event category.
* action: The Google Analytics Event action (likely no reason to change this given what the function is for).
* label: The Google Analytics Event label (useful for categorizing events).
* debug: Logs information to the console
* xMin: The minimum amount of the element to be visible in the viewport to count (if the selector is "body" then this can't be set and will be bottom of page)
* yMin: The minimum amount of the element to be visible in the viewport to count (if the selector is "body" then this can't be set and will be bottom of page)
*
* @return {function}
*/
read: function(opts) {
opts = this.extend({
"_method": "read",
"minTime": 10,
"selector": "body",
"xMin": 0,
"yMin": 1,
// for GA events specifically
"category": "behavior",
"action": "read:page",
"label": ""
}, opts);
var start = new Date().getTime();
var enoughTimeHasPassed = false;
var sentEvent = false;
var hasScrolledFarEnough = false;
var tbpContext = this;
// Every 2 seconds, check the conditions and send the event if satisfied.
setInterval(function(){
var end = new Date().getTime();
if((end - start) > (opts.minTime*1000)) {
if(!enoughTimeHasPassed) {
tbpContext.log("Tbp.read() " + opts.minTime + " seconds have passed", "info");
}
enoughTimeHasPassed = true;
}
if(hasScrolledFarEnough === true && enoughTimeHasPassed === true) {
// Send an event to Google Analytics.
if(sentEvent === false) {
// Note: All options get on the bus. This way anything that's listening gets a report back of what options were passed
// to the method as well as which Telephatic Black Panther method was called (via the "_method" option).
tbpContext.emitEvent(opts);
}
sentEvent = true;
}
},(2*1000));
var elem = $ki(opts.selector).first();
$ki(document).on('scroll', function() {
if(opts.selector === "body") {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
$ki(document).off('scroll');
tbpContext.log("Tbp.read() The user has scrolled to the bottom of the page", "info");
hasScrolledFarEnough = true;
}
} else {
if(elem.isOnScreen(opts.xMin,opts.yMin)) {
$ki(document).off('scroll');
tbpContext.log("Tbp.read() The user has scrolled far enough down the page", "info");
hasScrolledFarEnough = true;
}
}
});
},
/**
* Reports how much of the page came into the browser's viewport in a series of percentages.
* 25%, 50%, 75%, and 100%
*
* Unlike the read() function, this one does not take time into account. It only cares how far
* down the page a user scrolled. It could be useful for building aggregate reports that could
* shed light on how much of a site is seen/used. It could also shed light on abandonment; perhaps
* a page has too much text on it and readers give up after a certain point.
*
* Note: This won't work for well for short pages. It will simply report 100% of the page came into
* view or perhaps 50% and then 100%.
*
* @param {object} opts
* minTime: The amount of time, in seconds, that must pass before the event is considered valid (estimated time to read the content?)
* initialScrollRequired: If the user first must scroll in order for anything to count (default is true, this helps with short pages)
* category: The Google Analytics category
* action: The Google Analytics action
* hitCallback: Optional callback function
*
* @return {function}
*/
scrolledPage: function(opts) {
opts = this.extend({
"_method": "scrolledPage",
"minTime": 2,
"initialScrollRequired": true,
"category": "behavior",
"action": "scroll:page"
}, opts);
var tbpContext = this;
var sent = {};
var send = function(label) {
opts.label = label;
if(sent[label] === undefined) {
sent[label] = true;
// tbpContext.log("Tbp.scrolledPage() Logging page scroll event, label: " + label);
tbpContext.emitEvent(opts);
}
};
var percent = 0;
var hasScrolled = false;
var hasScrolledFn = function() {
hasScrolled = true;
};
window.addEventListener("scroll", hasScrolledFn);
// TODO: Think about checking if the window position started at the top of the page...
// The idea was to prevent events from being logged about 25% scroll when the user started at the bottom of the page (a refresh for example or perhaps anhor link)
// Though the page technically has scrolled down that far. It's the semantic difference between "has scrolled to this point" vs. "has seen up to this point"
// ...which is of course completely different yet from "has actually read everything up to this point"
// console.dir(tbpContext.windowTopOnLoad);
// Check for this every two seconds. In fact, check the current scroll position each time rather than at any point.
// This would negate situations where a user quickly scrolled down and back up again. While we aren't concerned with
// an actual "read" per se, we also want to do the best we can to avoid inaccuracies.
// The "minTime" option also helps avoid tracking a user who comes to the page, scrolls real quick and leaves.
// Again, not a "read" and so that "minTime" is meant to be short, but not zero. Though it could be set to zero of course.
// One last reason here -- Google Analytics will throttle events if too many are sent too quickly.
setTimeout(function() {
var intervalId = setInterval(function() {
// Only actually send something if the user has scrolled. If the user has not yet scrolled, don't do anything.
// hasScrolled wil lbe true if the user has scrolled yet and it gets checked on this function's interval (every 2 seconds).
// The reason for this check is because shorter pages may immediately meet the conditions for 25%, 50%, 75%, even 100% and
// otherwise be automatically recorded when the user didn't actaully scroll. This may be desireable though, so the options
// can bypass this check with `opts.initialScrollRequired` set to false.
if(hasScrolled || !opts.initialScrollRequired) {
percent = tbpContext.analysis.currentScrollPosition(true);
if(percent >= 0.25) {
send("25%");
}
if(percent >= 0.5) {
send("50%");
}
if(percent >= 0.75) {
send("75%");
}
// Note: 98% will be considered close enough to 100% - there may even be times 100% isn't possible.
if(percent >= 0.98) {
send("100%");
// We can also stop checking at this point. It is theoretically possible the user quickly scrolled to the bottom of the page
// and could still hit a lower scroll percentage, but it's better to stop checking to not get in the way of anything else
// that may be running on the page.
clearInterval(intervalId);
window.removeEventListener("scroll", hasScrolledFn);
}
}
},(2*1000));
},(opts.minTime*1000));
},
/**
* Tracks a click on a link that takes a user away from the page.
* This ensures the hit is recorded before directing the user onward.
*
* Note: The elementEvent is a required option. Passing the element is preferred, but it
* should be available through the event. These are both very easily retrieved with $ki or jQuery, etc.
*
* They are needed because propagation needs to be stopped and a new simulated click event needs
* to be triggered on the original element. This new click event will carry with it a new custom
* property that is checked for by this function in order to prevent a loop.
*
* It's not a real challenge for links opening in a new window, but for those that open in the
* same window, we need to ensure our event is emitted and passed off to Google Analytics before
* the browser is allowed to direct the visitor away from the page.
*
* Example usage can be found in auto_detect.js.
*
* @param {Object} opts
* element: The (likely anchor) element with the link out
* elementEvent: The event (likely MouseEvent on click) so that it can be cancelled while the event gets emitted
* trackDomainOnly: Just send the domain name to Google Analytics as the label instead of the full URL
* category: The Google Analytics Event category
* action: The Google Analytics Event action
* label: The Google Analytics Event label (optional, this will be the URL by default)
* debug: Logs information to the console
*
* @return {function} If a callback was specified, otherwise it redirects the user
*/
linkOut: function(opts) {
opts = this.extend({
"_method": "linkOut",
"element": false,
"elementEvent": false,
"trackDomainOnly": false,
"category": "navigation",
"action": "outbound",
"label": ""
}, opts);
if(opts.elementEvent === undefined) {
return;
}
// If an element was not passed, the event should have a target we can use...
if(!opts.element) {
opts.element = opts.elementEvent.target;
}
// Still no element? Really?
if(!opts.element) {
return;
}
var tbpContext = this;
// When we set events, we add an extra property that prevents a loop...Because for links, we typically watch the click event and also dispatch a new one.
if(opts.elementEvent.hasOwnProperty('preventLoop')) {
return;
}
// Manually dispatch a (new, since we can't use the old one) click event on the element.
var continueLinkOut = function() {
tbpContext.addEvent(opts.element, 'click', function(){return;});
tbpContext.triggerEvent(opts.element, 'click');
return;
};
// By default the label is going to be the link out.
var label = opts.element.href;
var tmp = document.createElement('a');
tmp.href = opts.element.href;
// Check to ensure this is an outbound link
if(tmp.hostname.toLowerCase() === window.location.host.toLowerCase()) {
return;
}
// preventDefault() if the link target is not _blank because we need to ensure the event is sent to GA and handled
// by anything else before the page disappears.
opts.elementEvent.preventDefault();
opts.elementEvent.stopPropagation();
if(opts.trackDomainOnly === true) {
label = tmp.hostname;
}
// But that can be overridden by the call by passing a label value.
if(opts.label !== "") {
label = opts.label;
}
// Set label (whatever it is at this point) to opts so it can be passed to the panther bus as part of a single object.
opts.label = label;
opts.hitCallback = opts.hitCallback || function() {
// Redirect if target is not _blank, on this callback (after the event has been emitted).
if(opts.element && opts.element.target !== '_blank') {
// tbpContext.log("Tbp.linkOut() The user will now be redirected to " + opts.element.href);
if(opts.debug) {
return setTimeout(function(){
continueLinkOut();
return;
}, 5000);
} else {
continueLinkOut();
return;
}
} else {
continueLinkOut();
return;
}
};
tbpContext.emitEvent(opts);
},
/**
* Detects a chance in the URL hash. This is common for single-page JavaScript apps with
* routing such as AngularJS.
*
* Google Analytics doesn't track these by default (Google Tag Manager has some support around it though).
* It's also useful for anchor links on page, #about #sectionA #sectionB etc.
* Sometimes pages contain various sections with an index up top. These are considered separate but are
* on the same page so GA doens't see that. In addition to seeing how far down the page a visitor scrolled,
* this will help show what content was consumed by a visitor.
*
* @param {Object} opts
*/
hashChange: function(opts) {
opts = this.extend({
"_method": "hashChange",
"category": "navigation",
"action": "hashbang",
"label": ""
}, opts);
var tbpContext = this;
window.onhashchange = function() {
// The label will be the hash value and the event will only be emitted if the hash value exists. It will include and start with #.
opts.label = window.location.hash;
if(opts.label && opts.label.length > 1) {
tbpContext.emitEvent(opts);
}
};
},
/**
* Detects when a user presses forward or backward on their browser.
* This can be useful in understanding if a web site has good UX or not.
* If a user can't navigate their way around the site with ease, they may feel the need to use
* their browser's back/forward button. Arguably this is why browsers have such buttons, but the
* counter argument to that is it's not very easy to do that on mobile devices where the browser bar
* (and back/forward controls) are often hidden in favor of screen space. Android devices do allow the
* system back button to navigate backwards (though there's no system forward button).
*
* So the backward/forward button press can actually be important and telling of UX depending on the situation.
*
* Like linkOut() the label in this case will be the URL. This
*
* @param {Object} opts
*/
historyNavigate: function(opts) {
opts = this.extend({
"_method": "historyNavigate",
"category": "navigation",
"action": "history",
"label": ""
}, opts);
var tbpContext = this;
// http://stackoverflow.com/questions/4570093/how-to-get-notified-about-changes-of-the-history-via-history-pushstate
// I'm not sure this will be supported in IE9...
// Perhaps hashchange: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange
// But for now I think this will work. It is supported well enough to be statistically relevant and helpful.
// There is also: https://github.com/browserstate/history.js - but that's a good bit of code to bring in.
// Google Tag Manager can also track this stuff: http://www.simoahava.com/analytics/google-tag-manager-history-listener/
(function(history){
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate === "function") {
history.onpushstate({state: state});
}
// ... whatever else you want to do
// maybe call onhashchange e.handler
return pushState.apply(history, arguments);
};
})(window.history);
window.onpopstate = history.onpushstate = function(e) {
// Can't tell if we went forward or backward. Just that the history state has changed.
tbpContext.emitEvent(opts);
};
},
/**
* Detects when the mouse cursor has left a particular element on the page (or the page itself).
* Basically a proxy to the browser's `mouseleave` event with some configurable conditions.
*
* By default, the entire document. This loosely detects the user's disinterest or attempt to use their navbar
* since it looks at mouseleave with regard to the y axis. If the user moves their mouse out of the window
* at the top, this sends an event.
*
* It's how ouibounce works (https://github.com/carlsednaoui/ouibounce) and it's the best guess that can be made.
* There are many uncaught scenarios.
*
* Of course don't forget visitors can go to another site by clicking on a link on the page too.
* Though TBP knows about that through other functions.
*
* The problem here is that a user could also be bookmarking the site =) That's not exactly disinterest or abandonment.
* Even if a timer is used here, it could take time for the user to organize the bookmark. It takes no time at all
* to click the browser's "home" button or "back" button. So a timer would have missed those.
*
* The only real guaranteed way is through `onbeforeunload` - the browser's event on exit, but that dialog can't be styled.
*
* Regardless, it is possible to respond to the `mouseleave` event fired by browsers and emit that as an event.
*
* Note: The "delay" option here makes
*
* @param {Object} opts Various options including the category, action, label for the event (for GA)
* trackOnce: If true (default), sets a cookie so the event may only occur once.
*
* perPage: If true, the "trackOnce" is per page not site wide (cookie path gets set).
*
* delay: An important one to note, it sets a time (ms) to count the leave, if a user moves their
* mouse back into the element before the delay, it resets. So in order to "count" as a "leave"
* the user must have left and not came back for this period of time (default 1 second).
*
* minTime: Like delay, except this is the minimum amount of time that must pass before even considering
* something to be a valid mouseleave. Meaning, the page must be loaded for this period of time.
* This prevents mouseleave events on non-engagement. If a visitor loads the page but navigates
* away real quick, it shouldn't be counted. Or should it? This adjusts that (default: 3 seconds).
* Note: This isn't on DOM ready, this timer starts upon this function being called.
*
* proximity: How close the mouse cursor needs to be from the edge of the element {top: 0, right: 0, bottom: 0, left: 0}
*
* element: Which element to watch (by default the entire page frame is watched)
*
* label: Set this to a string that makes sense for the element. You'll want to use it to segment your analytics and
* run reports, so make sure it's something useful and relevant.
*/
leave: function(opts) {
opts = this.extend({
"_method": "leave",
"category": "behavior",
"action": "",
"label": "",
"nonInteraction": true,
"trackOnce": true,
"perPage": true,
"delay": 1000,
"minTime": 3000,
// TODO: Proximity.
"proximity": {top: 0, right: 0, bottom: 0, left: 0},
"element": document.documentElement
}, opts);
// Ensure an element was passed.
if(opts.element === "") {
return;
}
var tbpContext = this;
// The best we can do, probably not a good idea to pass an element without an id. Unless it's unique HTML code (which <html> and <body> will be of course).
var elemId = opts.element.id || opts.element.outerHTML;
var leaveKey = '_tbp_' + this.hashCode("leave_" + elemId, true);
var path = opts.perPage ? window.location.pathname:"/";
var _delayTimer = null;
// Determine the action:actor value if not passed explicitly in the options.
// If the element is not the page/document, then use its ID (if available) as the actor in the action:actor value.
if(opts.action === "") {
if(opts.element !== document.documentElement) {
opts.action = "mouseleave:" + this.getTargetName(opts.element);
} else {
opts.action = "mouseleave:page";
}
}
var cookies = this.cookies;
setTimeout(function() {
setTimeout(function() {
opts.element.addEventListener('mouseleave', function(e) {
// TODO:
// Need to get the position of the element on the page and its bounding box to get the boundaries relative to the page then then subtract
// from the cursor position which is already relative to the page to get distance to the edge of the element.
// Then check against the proximity.
//
// if(e.clientY > opts.proximity.top || e.clientY < opts.proximity.bottom || e.clientX > opts.proximity.right || e.clientX < opts.proximity.left) {
// console.log("Not pushing event yet");
// console.log("Y: " + e.clientY + " X: " + e.clientX);
// return;
// }
// In the meantime, this will work just like ouibounce. Just set it when looking at the entire document.
// This default scenario is like ouibounce in that we are looking at when the visitor moves their cursor up toward the address bar
// or to a navigation button or perhaps even the menu or close button in their browser. Who knows...It's a guess really.
if(opts.element === document.documentElement) {
if(e.clientY > opts.proximity.top) {
return;
}
}
_delayTimer = setTimeout(function() {
if(!cookies.get(leaveKey)) {
// The label will contain the time in seconds it took to leave
opts.label = (tbpContext.timeSinceLoad()/1000);
tbpContext.log("Sending event for leaving.", "info");
tbpContext.emitEvent(opts);
} else {
tbpContext.log("Left, but event already sent.", "info");
}
if(opts.trackOnce) {
cookies.set(leaveKey, true, {path: path, expires: Infinity});
}
}, opts.delay);
});
opts.element.addEventListener('mouseenter', function() {
if (_delayTimer) {
clearTimeout(_delayTimer);
_delayTimer = null;
}
});
}, opts.delay);
}, opts.minTime);
},
/**
* Determines if a user has engaged with a form, but then abandoned
* it or had a difficult time completing it.
*
* Probably extremely handy on a checkout page.
*
* Maybe also have another function for time to fill out form.
* Or, similar to inactivity, a "hesitation" timer. Once a visitor clicks on a form, how long does it take them to complete it?
* Or once the mouse enters a button or certain section of a page, how long does it take for the visitor to click the CTA?
* For that matter, many of these events might be useful: http://www.clicktale.com/products/mouse-tracking-suite/link-analytics
* The problem in GA is matching the links to the data then telling/visually showing a reporter which link it was.
*
* Same issue exists for forms too...How many forms are on the page? How are they referenced/named?
* We can simply say, "A form on this page was abandoned" but what if there are multiple?
* So this may not be such an auotmatic thing...unless each form has an id of course.
*
*
* @param {Object} opts
*/
formAbandonment: function(opts) {
opts = this.extend({
"_method": "formAbandonment",
"category": "behavior",
"action": "formAbandonment",
"label": "",
"element": null
}, opts);
var tbpContext = this;
// We need a form element to be passed.
if(opts.element) {
}
},
/**
* Emits events for the period of time it took to complete a form.
*
* This can shed light on forms that may confuse visitors or otherwise create friction.
* Forms that take a long time to complete may need to be broken up or made easier for better UX.
*
* @param {Object} opts
*/
formCompletionTime: function(opts) {
opts = this.extend({
"_method": "formCompletionTime",
"category": "behavior",
"action": "formCompletionTime",
"label": "",
"element": null
}, opts);
var tbpContext = this;
// We need a form element to be passed.
if(opts.element) {
}
},
/**
* The time, in seconds, it took the visitor to engage with a given element and event type (click by default).
* This could be the time it took a visitor to click a button or it could be the time it took
* for a visitor to focus a form input field or to start typing into an input field.
* Time to click a call to action, time to login, register, etc.
*
* Note: If a form element is passed, a listener will be applied to all of its input fields looking
* for an onchange event.
*
* @param {Object} opts
*/
timeToEngage: function(opts) {
opts = this.extend({
"_method": "timeToEngage",
"category": "behavior",
"action": "timeToEngage",
"label": "",
"element": null,
"event": "click"
}, opts);
var tbpContext = this;
if(opts.element) {
opts.action = "timeToEngage:" + this.getTargetName(opts.element);
var tteFn = function() {
opts.element.removeEventListener(opts.event, tteFn);
opts.label = (tbpContext.timeSinceLoad()/1000);
tbpContext.emitEvent(opts);
};
if(opts.element.tagName.toLowerCase() === "form") {
// Special handler for forms. Each input field will have a listener, so this needs to remove itself from all other inputs for the parent form.
var tteFormFn = function(e) {
e.target.removeEventListener(e.type, tteFormFn);
for(var i=0; i < e.target.form.elements.length; i++) {
if(e.target.form.elements[i].type !== "fieldset") {
e.target.form.elements[i].removeEventListener(e.type, tteFormFn);
}
}
opts.label = (tbpContext.timeSinceLoad()/1000);
tbpContext.emitEvent(opts);
};
for(var i=0; i < opts.element.elements.length; i++) {
switch(opts.element.elements[i].type) {
default:
// We'll use focus over click, but other valid events include; keypress, keyup, keydown, and change
// For forms, the opts.event is applied to input fields.
// Note: Probably a bad idea for a web page to start a form input field off as being focused and
// in such a case, "change" may be a better event. Personally, I think "change" is the best event
// because clicking happens by "accident" sometimes. So auto_detect.js will use change.
if(opts.event === "click") {
opts.event = "focus";
}
opts.element.elements[i].addEventListener(opts.event, tteFormFn);
break;
case "fieldset":
// nada
break;
case "submit":
// do nothing here for now - submit could navigate the user away and we'd have to hijack the process like linkOut()
// and I don't see the value in it just yet...input fields should be changed by now right?
//
// on click is the one to use here regardless of opts.event
// el.addEventListener("click", tteFn(el, "click"));
break;
}
}
} else {
opts.element.addEventListener(opts.event, tteFn);
}
}
},
/**
* Emits events for periods of inactivity on a page.
*
* If the visitor does not move their mouse or scroll or click or do anything at all for specific
* intervals of time, events get emitted. These periods of time are configurable, by default they
* are 1 minute, 3 minute, and 5 minutes.
*
* Note: It is possible that the visitor is reading or watching a video. Though if they are reading,
* they likely should be moving their mouse or scroll the page. So adjust the timing accordingly.
*
* Though in the case of a video, this "inactivity" could actually be engagement. It could mean that
* the visitor is watching the video and therefore not moving their mouse.
*
* This inactivity timer can be paused in such cases:
* http://stackoverflow.com/questions/16755129/detect-fullscreen-mode
* https://gist.github.com/helgri/1336232
*
* Can also check for HTML5 video and if it is playing or not:
* http://stackoverflow.com/questions/8599076/detect-if-html5-video-element-is-playing
* By using: var stream = document.getElementsByTagName('video');
* Then looping those (might be multiple, but an array is always returned of course) and checking
* stream[0].paused ... if paused is false, then media is playing on the page.
*
* So when media is playing or when the browser is perhaps fullscreen we can stop this inactivity counter.
* We know the visitor is very likely engaged, just not moving their mouse around.
*
* Knowing when the page loaded (or when TBP was listening) we can also determine "true time on page"
* in that we can figure out how long until the visitor went inactive. Google Analytics reports time on
* page and it's a bit inaccurate. It's inaccurate if a user leaves their computer open while they are at lunch.
* It's inaccurate (I think) if visitors switch tabs because the timer is still going...But the visitor isn't
* actually looking at the page.
*
* If there is no mouse movmement, keys pressed, or scrolling...Then we know they aren't paying attention.
* We can assume the visitor left their computer to do something else physically. Or minimized the window.
* So we can record our own time on page event and provide some accuracy over Google Analytics.
*
* This also shows something interesting. If a visitor has a web page open in a tab they value it.
* They wanted to save it for later essentially. Possibly the immediate future. By looking at the pages
* where there was inactivity one might be able to make those more engaging. We know visitors are interested
* in these pages...Enough that they keep them open (just aren't actively looking at them). So how do we keep
* the visitor more engaged? If we incrased engagment on the page, would it help (determined by other data)?
* So this becomes a pretty cool metric.
*
* Of course the web page can also listen for this event and do something upon inactivity. Maybe encourage
* the visitor to engage...
*
* @param {Object} opts
*/
inactivity: function(opts) {
opts = this.extend({
"_method": "inactivity",
"category": "behavior",
"action": "inactive",
"label": "",
"nonInteraction": true,
"periods": [60, 180, 300],
// "timeToDisengage": 60, // can take lowest period for this. an event will be emitted that takes time since load to the period.
// that is how long it took a visitor to become inactive...so analytics reports can segment this. users are inactive after 3 minutes let's say.
// and then we can ignore the fact that time on page was 10 minutes. because google's time on page is inaccurate in that case.
"checkInterval": 2
}, opts);
var tbpContext = this;
var sent = {};
var timeSinceLastAcitivty = (new Date()).getTime();
var active = false;
var setActiveOnInput = function() {
active = true;
timeSinceLastAcitivty = (new Date()).getTime();
};
// All the events that tell us a visitor is actively engaging with the page.
window.addEventListener("scroll", setActiveOnInput);
window.addEventListener("mousemove", setActiveOnInput);
window.addEventListener("keypress", setActiveOnInput);
window.addEventListener("click", setActiveOnInput);
// Checks for inactivity on the `opts.checkInterval` which can be tuned for performance.
var inactivityCheck = setInterval(function() {
// Check to see if active is false, if so - there has been no activity.
if(!active) {
// Then check if enough time has passed for the periods defined in `opts.periods`
for(var i in opts.periods) {
if(opts.periods.hasOwnProperty(i)) {
var periodStr = opts.periods[i].toString();
if(!sent.hasOwnProperty(periodStr)) {
sent[periodStr] = false;
}
var now = (new Date()).getTime();
if(sent[periodStr] === false && ((now - timeSinceLastAcitivty) >= (opts.periods[i] * 1000))) {
opts.label = periodStr;
tbpContext.emitEvent(opts);
sent[periodStr] = true;
}
}
}
// watch all periods and once all have been reached. stop inactivityCheck and remove event listeners.
var stopWatching = true;
for(var j in sent) {
if(sent[j] === false) {
stopWatching = false;
}
}
if(stopWatching) {
// Keep it clean. Having all of these listeneers and the inactivity interval can affect performance.
tbpContext.log("Stop watching for activity, events for all periods have been sent.", "info");
clearInterval(inactivityCheck);
window.removeEventListener("scroll", setActiveOnInput);
window.removeEventListener("mousemove", setActiveOnInput);
window.removeEventListener("keypress", setActiveOnInput);
window.removeEventListener("click", setActiveOnInput);
}
}
// Set active to false. It will get set back to true if there is activity before the next pass.
active = false;
}, opts.checkInterval*1000);
}
};