Skip to content

Commit

Permalink
Merge pull request thorst#37 from rustygeldmacher/rg-timer-sync
Browse files Browse the repository at this point in the history
Support for syncing timers across tabs
  • Loading branch information
thorst committed Apr 8, 2016
2 parents cc32754 + fdd7725 commit bd38b32
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 57 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
####VERSION: 1.0.0
####VERSION: 1.1.0
####RELEASED: 03/10/2014
####AUTHOR: TODD HORST
-------
Expand Down Expand Up @@ -26,6 +26,7 @@ Added:
* `wheel` event. wheel is to replace DOMMouseScroll & mousewheel. We could remove DOMMouseScroll now as its technically not needed to support firefox n-1. mousewheel will stick around until we drop ie8.
* `MSPointerDown` `MSPointerMove` events #22
* ability to send only settings parameter
* Ability to sync timer state across browser tabs

Update:
* changed $().data() to jQuery.data() for speed
Expand Down Expand Up @@ -88,4 +89,4 @@ using the old version.

I thought about allowing users to send a callback function in, ala most jquery methods. However
after researching and thinking the callbacks would be synchronous and events are async. So we
will be sticking with the current method
will be sticking with the current method
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ $(function() {

// activity is any one of these events
events [default:'mousemove keydown wheel DOMMouseScroll mousewheel mousedown touchstart touchmove MSPointerDown MSPointerMove']

// If set, the use a localStorage key to sync activity across browser tabs/windows
timerSyncId [default:null]
});
```

Expand Down
14 changes: 10 additions & 4 deletions demos/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ <h4 class="modal-title" id="myModalLabel">Modal title</h4>

//Start timeout, passing no options
//Same as $.idleTimer(docTimeout, docOptions);
$(document).idleTimer(docTimeout);
$(document).idleTimer({
timeout: docTimeout,
timerSyncId: "document-timer-demo"
});

//For demo purposes, style based on initial state
if ($(document).idleTimer("isIdle")) {
Expand Down Expand Up @@ -327,7 +330,7 @@ <h4 class="modal-title" id="myModalLabel">Modal title</h4>
}
$(this).blur();
return false;
});
});
$("#btRemaining").click(function () {
$('#elStatus')
.val(function (i, v) {
Expand All @@ -345,7 +348,7 @@ <h4 class="modal-title" id="myModalLabel">Modal title</h4>
.scrollToBottom();
$(this).blur();
return false;
});
});
$("#btState").click(function () {
$('#elStatus')
.val(function (i, v) {
Expand All @@ -357,7 +360,10 @@ <h4 class="modal-title" id="myModalLabel">Modal title</h4>
});

//Clear value if there was one cached & start time
$('#elStatus').val('').idleTimer(taTimeout);
$('#elStatus').val('').idleTimer({
timeout: taTimeout,
timerSyncId: "element-timer-demo"
});

//For demo purposes, show initial state
if ($("#elStatus").idleTimer("isIdle")) {
Expand Down
314 changes: 314 additions & 0 deletions dist/idle-timer.1.1.0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*! Idle Timer - v1.1.0 - 2016-03-21
* https://github.com/thorst/jquery-idletimer
* Copyright (c) 2016 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") || {};

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;
}

/* (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($.trim((opts.events + " ").split(" ").join("._idleTimer ")), function (e) {
handleEvent(e);
});

if (opts.timerSyncId) {
$(window).bind("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);
2 changes: 2 additions & 0 deletions dist/idle-timer.1.1.0.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bd38b32

Please sign in to comment.