class for keyboard navigable AJAX widgets for better usability and accessibility
var QFocuser = (function() {
current safari doesnt support tabindex for elements, but chrome does. When Safari nightly version become current, this switch will be removed.
var supportTabIndexOnRegularElements = (function() {
var webKitFields = RegExp("( AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
if (!webKitFields || webKitFields.length < 3) return true; // every other browser support it
var versionString = webKitFields[2],
isNightlyBuild = versionString.indexOf("+") != -1;
if (isNightlyBuild || (/chrome/i).test(navigator.userAgent)) return true;
})();
return (supportTabIndexOnRegularElements ? function(widget, options) {
var isIE = document.attachEvent && !document.addEventListener,
focused,
previousFocused,
lastState,
widgetState,
widgetFocusBlurTimer;
options = (function() {
var defaultOptions = {
onFocus: function(el, e) { },
onBlur: function(el, e) { },
onWidgetFocus: function() { },
onWidgetBlur: function() { },
tabIndex: 0, // add tabindex to your widget to be attainable by tab key
doNotShowBrowserFocusDottedBorder: true
};
for (var option in options) defaultOptions[option] = options[option];
return defaultOptions;
})();
init();
something to make IE happy
if (isIE) {
window.attachEvent('onunload', function() {
window.detachEvent('onunload', arguments.callee);
widget.clearAttributes();
});
}
function init() {
setTabIndex(widget, options.tabIndex);
IE remembers focus after page reload but don’t fire focus
if (isIE && widget == widget.ownerDocument.activeElement) widget.blur();
toggleEvents(true);
};
function hasTabIndex(el) {
var attr = el.getAttributeNode('tabindex');
return attr && attr.specified;
};
function setTabIndex(el, number) {
var test = document.createElement('div');
test.setAttribute('tabindex', 123);
var prop = hasTabIndex(test) ? 'tabindex' : 'tabIndex';
(setTabIndex = function(el, number) {
el.setAttribute(prop, '' + number);
if (options.doNotShowBrowserFocusDottedBorder) hideFocusBorder(el);
})(el, number);
};
function getTabIndex(el) {
return hasTabIndex(el) && el.tabIndex;
};
function hideFocusBorder(el) {
if (isIE) el.hideFocus = true;
else el.style.outline = 0;
};
function toggleEvents(register) {
var method = register ? isIE ? 'attachEvent' : 'addEventListener' : isIE ? 'detachEvent' : 'removeEventListener';
if (isIE) {
widget[method]('onfocusin', onFocusBlur);
widget[method]('onfocusout', onFocusBlur);
}
else {
widget[method]('focus', onFocusBlur, true);
widget[method]('blur', onFocusBlur, true);
}
};
function onFocusBlur(e) {
e = e || widget.ownerDocument.parentWindow.event;
var target = e.target || e.srcElement;
lastState = { focusin: 'Focus', focus: 'Focus', focusout: 'Blur', blur: 'Blur'}[e.type];
filter bubling focus and blur events, only these which come from elements setted by focus method are accepted
if (target == focused || target == previousFocused) {
options['on' + lastState](target, e);
}
clearTimeout(widgetFocusBlurTimer);
widgetFocusBlurTimer = setTimeout(onWidgetFocusBlur, 10);
};
function onWidgetFocusBlur() {
if (widgetState == lastState) return;
widgetState = lastState;
options['onWidget' + widgetState]();
};
call this method only for mousedown, in case of mouse is involved (keys are ok)
function focus(el) {
if (focused) {
setTabIndex(focused, -1); // to disable tab walking in widget
previousFocused = focused;
}
else setTabIndex(widget, -1);
focused = el;
setTabIndex(focused, 0);
focused.focus();
};
call this method after updating widget content, to be sure that tab will be attainable by tag key
function refresh() {
var setIndex = getTabIndex(widget) == -1,
deleteFocused = true,
els = widget.getElementsByTagName('*');
for (var i = els.length; i--; ) {
var idx = getTabIndex(els[i]);
if (idx !== false && idx >= 0) setIndex = true;
if (els[i] === focused) deleteFocused = false;
}
if (setIndex) setTabIndex(widget, 0);
if (deleteFocused) focused = null;
};
function getFocused() {
return focused;
};
return element on which you should register key listeners
function getKeyListener() {
return widget;
};
function destroy() {
toggleEvents();
};
return {
focus: focus,
getFocused: getFocused,
getKeyListener: getKeyListener,
refresh: refresh,
destroy: destroy
}
} :
version for Safari, it mimics focus blur behaviour
function(widget, options) {
var focuser,
lastState,
widgetState = 'Blur',
widgetFocusBlurTimer,
focused;
options = (function() {
var defaultOptions = {
onFocus: function(el, e) { },
onBlur: function(el, e) { },
onWidgetFocus: function() { },
onWidgetBlur: function() { },
tabIndex: 0, // add tabindex to your widget to be attainable by tab key
doNotShowBrowserFocusDottedBorder: true
};
for (var option in options) defaultOptions[option] = options[option];
return defaultOptions;
})();
init();
function init() {
focuser = widget.ownerDocument.createElement('input');
var wrapper = widget.ownerDocument.createElement('span');
wrapper.style.cssText = 'position: absolute; overflow: hidden; width: 0; height: 0';
wrapper.appendChild(focuser);
it’s placed in to widget, to mimics tabindex zero behaviour, where element document order matter
widget.insertBefore(wrapper, widget.firstChild);
toggleEvent(true);
};
function toggleEvent(register) {
var method = register ? 'addEventListener' : 'removeEventListener';
focuser[method]('focus', onFocusBlur);
focuser[method]('blur', onFocusBlur);
window[method]('blur', onWindowBlur);
widgetmethod;
};
set active simulation
function onWidgetMousedown(e) {
if (widgetState == 'Blur') {
setTimeout(function() {
focuser.focus();
}, 1);
}
};
function onFocusBlur(e) {
lastState = e.type.charAt(0).toUpperCase() + e.type.substring(1);
if (focused) options['on' + lastState](focused, e);
clearTimeout(widgetFocusBlurTimer);
widgetFocusBlurTimer = setTimeout(onWidgetFocusBlur, 10);
};
function onWidgetFocusBlur() {
if (widgetState == lastState) return;
widgetState = lastState;
options['onWidget' + widgetState]();
};
safari is so stupid.. doesn’t fire blur event when another browser tab is switched
function onWindowBlur() {
focuser.blur();
};
function focus(el) {
setTimeout(function() {
focuser.blur();
setTimeout(function() {
focused = el;
focuser.focus();
}, 1);
}, 1);
};
function refresh() {
var deleteFocused = true,
els = widget.getElementsByTagName('*');
for (var i = els.length; i--; ) {
if (els[i] === focused) deleteFocused = false;
}
if (deleteFocused) focused = null;
};
function getFocused() {
return focused;
};
function getKeyListener() {
return focuser;
};
function destroy() {
toggleEvents();
};
return {
focus: focus,
getFocused: getFocused,
getKeyListener: getKeyListener,
refresh: refresh,
destroy: destroy
}
});
})();