Provides

QFocuser.js

class for keyboard navigable AJAX widgets for better usability and accessibility

License:
MIT-style license.
  1. 16
var QFocuser = (function() {

current safari doesnt support tabindex for elements, but chrome does. When Safari nightly version become current, this switch will be removed.

  1. 20
  2. 21
  3. 22
  4. 23
  5. 24
  6. 25
  7. 26
  8. 27
  9. 28
  10. 29
  11. 30
  12. 31
  13. 32
  14. 33
  15. 34
  16. 35
  17. 36
  18. 37
  19. 38
  20. 39
  21. 40
  22. 41
  23. 42
  24. 43
  25. 44
  26. 45
  27. 46
  28. 47
  29. 48
  30. 49
  31. 50
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

  1. 53
  2. 54
  3. 55
  4. 56
  5. 57
  6. 58
  7. 59
  8. 60
  9. 61
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

  1. 63
  2. 64
  3. 65
  4. 66
  5. 67
  6. 68
  7. 69
  8. 70
  9. 71
  10. 72
  11. 73
  12. 74
  13. 75
  14. 76
  15. 77
  16. 78
  17. 79
  18. 80
  19. 81
  20. 82
  21. 83
  22. 84
  23. 85
  24. 86
  25. 87
  26. 88
  27. 89
  28. 90
  29. 91
  30. 92
  31. 93
  32. 94
  33. 95
  34. 96
  35. 97
  36. 98
  37. 99
  38. 100
  39. 101
  40. 102
  41. 103
  42. 104
  43. 105
  44. 106
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

  1. 108
  2. 109
  3. 110
  4. 111
  5. 112
  6. 113
  7. 114
  8. 115
  9. 116
  10. 117
  11. 118
  12. 119
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)

  1. 122
  2. 123
  3. 124
  4. 125
  5. 126
  6. 127
  7. 128
  8. 129
  9. 130
  10. 131
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

  1. 134
  2. 135
  3. 136
  4. 137
  5. 138
  6. 139
  7. 140
  8. 141
  9. 142
  10. 143
  11. 144
  12. 145
  13. 146
  14. 147
  15. 148
  16. 149
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

  1. 152
  2. 153
  3. 154
  4. 155
  5. 156
  6. 157
  7. 158
  8. 159
  9. 160
  10. 161
  11. 162
  12. 163
  13. 164
  14. 165
  15. 166
  16. 167
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

  1. 170
  2. 171
  3. 172
  4. 173
  5. 174
  6. 175
  7. 176
  8. 177
  9. 178
  10. 179
  11. 180
  12. 181
  13. 182
  14. 183
  15. 184
  16. 185
  17. 186
  18. 187
  19. 188
  20. 189
  21. 190
  22. 191
  23. 192
  24. 193
  25. 194
  26. 195
  27. 196
  28. 197
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

  1. 199
  2. 200
  3. 201
  4. 202
  5. 203
  6. 204
  7. 205
  8. 206
  9. 207
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;

  1. 209
};

set active simulation

  1. 212
  2. 213
  3. 214
  4. 215
  5. 216
  6. 217
  7. 218
  8. 219
  9. 220
  10. 221
  11. 222
  12. 223
  13. 224
  14. 225
  15. 226
  16. 227
  17. 228
  18. 229
  19. 230
  20. 231
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

  1. 234
  2. 235
  3. 236
  4. 237
  5. 238
  6. 239
  7. 240
  8. 241
  9. 242
  10. 243
  11. 244
  12. 245
  13. 246
  14. 247
  15. 248
  16. 249
  17. 250
  18. 251
  19. 252
  20. 253
  21. 254
  22. 255
  23. 256
  24. 257
  25. 258
  26. 259
  27. 260
  28. 261
  29. 262
  30. 263
  31. 264
  32. 265
  33. 266
  34. 267
  35. 268
  36. 269
  37. 270
  38. 271
  39. 272
  40. 273
  41. 274
  42. 275
  43. 276
  44. 277
  45. 278
  46. 279
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 } }); })();