340 lines
10 KiB
JavaScript
Raw Permalink Normal View History

/*! Idle Timer - v1.1.1 - 2020-06-25
* https://github.com/thorst/jquery-idletimer
* Copyright (c) 2020 Paul Irish; Licensed MIT */
/*
mousewheel (deprecated) -> IE6.0, Chrome, Opera, Safari
DOMMouseScroll (deprecated) -> Firefox 1.0
wheel (standard) -> Chrome 31, Firefox 17, IE9, Firefox Mobile 17.0
//No need to use, use DOMMouseScroll
MozMousePixelScroll -> Firefox 3.5, Firefox Mobile 1.0
//Events
WheelEvent -> see wheel
MouseWheelEvent -> see mousewheel
MouseScrollEvent -> Firefox 3.5, Firefox Mobile 1.0
*/
(function ($) {
$.idleTimer = function (firstParam, elem) {
var opts;
if (typeof firstParam === 'object') {
opts = firstParam;
firstParam = null;
} else if (typeof firstParam === 'number') {
opts = { timeout: firstParam };
firstParam = null;
}
// element to watch
elem = elem || document;
// defaults that are to be stored as instance props on the elem
opts = $.extend(
{
idle: false, // indicates if the user is idle
timeout: 30000, // the amount of time (ms) before the user is considered idle
events:
'mousemove keydown wheel DOMMouseScroll mousewheel mousedown touchstart touchmove MSPointerDown MSPointerMove' // define active events
},
opts
);
var jqElem = $(elem),
obj = jqElem.data('idleTimerObj') || {},
/* (intentionally not documented)
* Toggles the idle state and fires an appropriate event.
* @return {void}
*/
toggleIdleState = function (e) {
var obj = $.data(elem, 'idleTimerObj') || {};
// toggle the state
obj.idle = !obj.idle;
// store toggle state date time
obj.olddate = +new Date();
// create a custom event, with state and name space
var event = $.Event((obj.idle ? 'idle' : 'active') + '.idleTimer');
// trigger event on object with elem and copy of obj
$(elem).trigger(event, [elem, $.extend({}, obj), e]);
},
/**
* Handle event triggers
* @return {void}
* @method event
* @static
*/
handleEvent = function (e) {
var obj = $.data(elem, 'idleTimerObj') || {};
// ignore writting to storage unless related to idleTimer
if (e.type === 'storage' && e.originalEvent.key !== obj.timerSyncId) {
return;
}
// this is already paused, ignore events for now
if (obj.remaining != null) {
return;
}
/*
mousemove is kinda buggy, it can be triggered when it should be idle.
Typically is happening between 115 - 150 milliseconds after idle triggered.
@psyafter & @kaellis report "always triggered if using modal (jQuery ui, with overlay)"
@thorst has similar issues on ios7 "after $.scrollTop() on text area"
*/
if (e.type === 'mousemove') {
// if coord are same, it didn't move
if (e.pageX === obj.pageX && e.pageY === obj.pageY) {
return;
}
// if coord don't exist how could it move
if (typeof e.pageX === 'undefined' && typeof e.pageY === 'undefined') {
return;
}
// under 200 ms is hard to do, and you would have to stop, as continuous activity will bypass this
var elapsed = +new Date() - obj.olddate;
if (elapsed < 200) {
return;
}
}
// clear any existing timeout
clearTimeout(obj.tId);
// if the idle timer is enabled, flip
if (obj.idle) {
toggleIdleState(e);
}
// store when user was last active
obj.lastActive = +new Date();
// update mouse coord
obj.pageX = e.pageX;
obj.pageY = e.pageY;
// sync lastActive
if (e.type !== 'storage' && obj.timerSyncId) {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(obj.timerSyncId, obj.lastActive);
}
}
// set a new timeout
obj.tId = setTimeout(toggleIdleState, obj.timeout);
},
/**
* Restore initial settings and restart timer
* @return {void}
* @method reset
* @static
*/
reset = function () {
var obj = $.data(elem, 'idleTimerObj') || {};
// reset settings
obj.idle = obj.idleBackup;
obj.olddate = +new Date();
obj.lastActive = obj.olddate;
obj.remaining = null;
// reset Timers
clearTimeout(obj.tId);
if (!obj.idle) {
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
},
/**
* Store remaining time, stop timer
* You can pause from an idle OR active state
* @return {void}
* @method pause
* @static
*/
pause = function () {
var obj = $.data(elem, 'idleTimerObj') || {};
// this is already paused
if (obj.remaining != null) {
return;
}
// define how much is left on the timer
obj.remaining = obj.timeout - (+new Date() - obj.olddate);
// clear any existing timeout
clearTimeout(obj.tId);
},
/**
* Start timer with remaining value
* @return {void}
* @method resume
* @static
*/
resume = function () {
var obj = $.data(elem, 'idleTimerObj') || {};
// this isn't paused yet
if (obj.remaining == null) {
return;
}
// start timer
if (!obj.idle) {
obj.tId = setTimeout(toggleIdleState, obj.remaining);
}
// clear remaining
obj.remaining = null;
},
/**
* Stops the idle timer. This removes appropriate event handlers
* and cancels any pending timeouts.
* @return {void}
* @method destroy
* @static
*/
destroy = function () {
var obj = $.data(elem, 'idleTimerObj') || {};
//clear any pending timeouts
clearTimeout(obj.tId);
//Remove data
jqElem.removeData('idleTimerObj');
//detach the event handlers
jqElem.off('._idleTimer');
},
/**
* Returns the time until becoming idle
* @return {number}
* @method remainingtime
* @static
*/
remainingtime = function () {
var obj = $.data(elem, 'idleTimerObj') || {};
//If idle there is no time remaining
if (obj.idle) {
return 0;
}
//If its paused just return that
if (obj.remaining != null) {
return obj.remaining;
}
//Determine remaining, if negative idle didn't finish flipping, just return 0
var remaining = obj.timeout - (+new Date() - obj.lastActive);
if (remaining < 0) {
remaining = 0;
}
//If this is paused return that number, else return current remaining
return remaining;
};
// determine which function to call
if (firstParam === null && typeof obj.idle !== 'undefined') {
// they think they want to init, but it already is, just reset
reset();
return jqElem;
} else if (firstParam === null) {
// they want to init
} else if (firstParam !== null && typeof obj.idle === 'undefined') {
// they want to do something, but it isnt init
// not sure the best way to handle this
return false;
} else if (firstParam === 'destroy') {
destroy();
return jqElem;
} else if (firstParam === 'pause') {
pause();
return jqElem;
} else if (firstParam === 'resume') {
resume();
return jqElem;
} else if (firstParam === 'reset') {
reset();
return jqElem;
} else if (firstParam === 'getRemainingTime') {
return remainingtime();
} else if (firstParam === 'getElapsedTime') {
return +new Date() - obj.olddate;
} else if (firstParam === 'getLastActiveTime') {
return obj.lastActive;
} else if (firstParam === 'isIdle') {
return obj.idle;
}
// Test via a getter in the options object to see if the passive property is accessed
// This isnt working in jquery, though is planned for 4.0
// https://github.com/jquery/jquery/issues/2871
/*var supportsPassive = false;
try {
var Popts = Object.defineProperty({}, "passive", {
get: function() {
supportsPassive = true;
}
});
window.addEventListener("test", null, Popts);
} catch (e) {}
*/
/* (intentionally not documented)
* Handles a user event indicating that the user isn't idle. namespaced with internal idleTimer
* @param {Event} event A DOM2-normalized event object.
* @return {void}
*/
jqElem.on((opts.events + ' ').split(' ').join('._idleTimer ').trim(), function (e) {
handleEvent(e);
});
//}, supportsPassive ? { passive: true } : false);
if (opts.timerSyncId) {
$(window).on('storage', handleEvent);
}
// Internal Object Properties, This isn't all necessary, but we
// explicitly define all keys here so we know what we are working with
obj = $.extend(
{},
{
olddate: +new Date(), // the last time state changed
lastActive: +new Date(), // the last time timer was active
idle: opts.idle, // current state
idleBackup: opts.idle, // backup of idle parameter since it gets modified
timeout: opts.timeout, // the interval to change state
remaining: null, // how long until state changes
timerSyncId: opts.timerSyncId, // localStorage key to use for syncing this timer
tId: null, // the idle timer setTimeout
pageX: null, // used to store the mouse coord
pageY: null
}
);
// set a timeout to toggle state. May wish to omit this in some situations
if (!obj.idle) {
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
// store our instance on the object
$.data(elem, 'idleTimerObj', obj);
return jqElem;
};
// This allows binding to element
$.fn.idleTimer = function (firstParam) {
if (this[0]) {
return $.idleTimer(firstParam, this[0]);
}
return this;
};
})(jQuery);