Currently, I'm working on a project where I needed to run some code every time the page is hidden.
I searched high and low and found this question: Is there a way to detect if a browser window is not currently active?. The accepted answer did an amazing job, but it only sets a class, doesn't run a function.
So, with that need, I took off from that code and made an API around it:
;(function(window){
//Name of the property used by the browser
var hidden = 'hidden';
//Internal status, to know if the page is visible or not
//This will hold the current page visibility status, in case someone runs .fire()
var status;
var handlers = {
visible: [],
hidden: [],
change: []
};
var fire = function(event){
var fired_status = event === 'change' ? status : event;
//Prevents external changes from causing off-by-n bugs
var list = handlers[event];
var length = list.length;
for(var i = 0; i < length; i++)
{
if(list[i])
{
list[i].call(window, fired_status);
}
}
};
//=========================================================================================
// Core code, taken from https://stackoverflow.com/q/1060008/
// Changed to my own taste and needs
//Only runs when the status changes, so, we trigger the change here too
var onchange = function(event){
var map = {
focus: 'visible',
focusin: 'visible',
pageshow: 'visible',
blur: 'hidden',
focusout: 'hidden',
pagehide:'hidden'
};
event = event || window.event;
//We need to keep the internal status updated
status = map[event.type] || (this[hidden] ? 'hidden' : 'visible');
fire(status);
fire('change');
};
if (hidden in document)
{
document.addEventListener('visibilitychange', onchange);
}
else if ((hidden = 'mozHidden') in document)
{
document.addEventListener('mozvisibilitychange', onchange);
}
else if ((hidden = 'webkitHidden') in document)
{
document.addEventListener('webkitvisibilitychange', onchange);
}
else if ((hidden = 'msHidden') in document)
{
document.addEventListener('msvisibilitychange', onchange);
}
else if ('onfocusin' in document)
{
document.onfocusin = document.onfocusout = onchange;
}
else
{
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;
}
status = document[hidden] === undefined ? null : (document[hidden] ? 'hidden' : 'visible');
//=========================================================================================
if(status !== null)
{
var fn = function(){
if(status === 'hidden')
{
fire('hidden');
}
window.removeEventListener('load', fn);
};
window.addEventListener('load', fn);
}
window.PageVisibility = {
//jQuery - style handling
visible: function(handler){
if(handler instanceof window.Function)
{
handlers['visible'][handlers['visible'].length] = handler;
}
else
{
fire('visible');
}
},
//plain style (eg: obj.onevent = handler)
set onvisible(handler){
handlers['visible'] = handler instanceof window.Function ? [handler] : [];
},
get onvisible(){
return handlers['visible'][0];
},
hidden: function(handler){
if(handler instanceof window.Function)
{
handlers['hidden'][handlers['hidden'].length] = handler;
}
else
{
fire('hidden');
}
},
set onhidden(handler){
handlers['hidden'] = handler instanceof window.Function ? [handler] : [];
},
get onhidden(){
return handlers['hidden'][0];
},
visibilitychange: function(handler){
if(handler instanceof window.Function)
{
handlers['change'][handlers['change'].length] = handler;
}
else
{
fire('change');
}
},
set onvisibilitychange(handler){
handlers['change'] = handler instanceof window.Function ? [handler] : [];
},
get onvisibilitychange(){
return handlers['change'][0];
},
//Modern style
addEventListener: function(event, handler){
if(!(event in handlers) || !(handler instanceof window.Function))
{
return false;
}
handlers[event][handlers[event].length] = handler;
return true;
},
removeEventListener: function(event, handler){
if(!(event in handlers) || !(handler instanceof window.Function))
{
return false;
}
if(handler)
{
for(var i = 0, l = handlers[event].length; i < l; i++)
{
if(handlers[event][i] === handler)
{
delete handlers[event][i];
}
}
}
else
{
handlers[event] = [];
}
return true;
},
//Should return null only when it is impossible to check if the page is hidden
isVisible: function(){
return status === null ? null : status === 'visible';
},
fire: function(){
if(status !== null)
{
fire(status);
}
},
//May be useful for someone
hiddenPropertyName: hidden
};
})(Function('return this')());
The code is quite long but most of it is just whitespace.
The code allows to use jQuery-style event handling, direct-attribution style (or old style) and the "modern" addEventListener style.
I know the code isn't compatible with IE8, but I only need to support IE9 and up. Any other browser that works is perfectly fine.
My major area of concert is all the repeated code on the event handling. I couldn't think of a way to keep it easy and clean.
Performance might be an issue as well.
In terms of readability, clarity and performance, what else can I improve?
window.PageVisibilityobject don't return anything, they just read an array valueget onvisible(){ handlers['visible'][0]; }. Could you explain why ? \$\endgroup\$return-.- I've ninja-edited it. \$\endgroup\$