Skip to content
Browse files

Repacked QFocuser to work with packager

  • Loading branch information...
0 parents commit 9e8396c63e95319a256d6b9821ba596166f71bf0 @Inviz committed Aug 26, 2010
Showing with 349 additions and 0 deletions.
  1. +63 −0 README
  2. +278 −0 Source/QFocuser.js
  3. +8 −0 package.yml
63 README
@@ -0,0 +1,63 @@
+QFocuser 0.95 - class for keyboard navigable AJAX widgets for better usability and accessibility
+
+ Features:
+ - allow to your widget listen key events when its focused
+ - focus can be enabled on any element
+ - fires focus and blur events (so your table row will NOT remain highlighted after click out of table for example)
+ - make your widget to be accessible by tab key
+ - tiny and library agnostic
+ - this implementation could serve as a model for the others
+ - works also in Safari!
+
+ Tested on: IE6/7, FF2/3, Safari, Webkit, Opera and Chrome
+
+Key navigation is important part in overall usability and accessibility of all applications.
+ If you have only one widget, key events can be safely attached to document, but if more than one widgets
+ are involved, each widget has to react to keyboard separately, which means when it has focus.
+ Also, each widget has to react when focus is lost (remove highlighted row for example).
+ There is no another solution than listen blur event. Any other solution based e.g. on mouseclick will fail with iframes.
+
+ .. read this: http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/writing-your-own-widget-class/creating-accessi
+
+ How it should work:
+ Key tab switches between your widgets, in order defined by tabIndex. Focused widget can listen key events.
+
+ How it work:
+ To make elements focusable, set them tabindex. That will allow you to attach keyboard events to them too.
+ The tabindex value can allow for some interesting behavior. If given a value of "-1", the element can't be tabbed
+ to but focus can be given to the element programmatically (using element.focus()).
+ If given a value of 0, the element can be focused via the keyboard and falls into the tabbing flow of the document.
+ Values greater than 0 create a priority level with 1 being the most important.
+
+ Safari issue:
+ Current version of safari doesn't support tabIndex for regular elements. WebKit nightly build does.
+ This library has a workaround for it, to have almost same behavior.
+
+ Hiding the Browsers Focus Borders issue:
+ All browser including IE6/7 will show pesky dotted borders around focused elements for accessibility reasons.
+ The dotted visual clutters up the design. To remove them, use options doNotShowBrowserFocusDottedBorder.
+ It has to be done in code, because Internet Explorer has no style property for that. If you are remove them, do not
+ forget use own focused element highlighting.
+
+ Example:
+
+ var focuser = new QFocuser(widgetContainer, {
+ onFocus: function(focusedEl) { .. add highlighted class or whatever }
+ onBlur: function(focusedEl) { .. remove highlighted class or whatever }
+ onKeydown: e.g. handleArrows...
+ });
+ // attach your own keys listeners
+ $(focuser.getEl()).addEvent('keydown', e.g. handleArrows);
+ // when your widget decided to set the focus (and then receive key events)
+ focuser.focus(tableRowForExample);
+
+ Links:
+
+ http://snook.ca/archives/accessibility_and_usability/elements_focusable_with_tabindex/
+ http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets
+ http://dev.aol.com/dhtml_style_guide - recommended key shortcuts
+ http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+
+ License: MIT-style license. Copyright: Copyright (c) 2009 Daniel Steigerwald, daniel.steigerwald.cz
+
+
278 Source/QFocuser.js
@@ -0,0 +1,278 @@
+/*
+---
+
+script: QFocuser.js
+
+description: class for keyboard navigable AJAX widgets for better usability and accessibility
+
+license: MIT-style license.
+
+provides: [QFocuser]
+
+...
+*/
+
+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, 0);
+ // 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);
+ widget[method]('mousedown', onWidgetMousedown);
+ };
+
+ // 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
+ }
+
+ });
+
+})();
8 package.yml
@@ -0,0 +1,8 @@
+name: QFocuser
+filename: qfocuser.js
+description: Native focus behaviour emulator
+license: MIT-Style License, http://mootools.net/license
+copyright: 2009 Daniel Steigerwald, daniel.steigerwald.cz
+authors: Daniel Steigerwald
+sources:
+ - Source/QFocuser.js

0 comments on commit 9e8396c

Please sign in to comment.
Something went wrong with that request. Please try again.