This repository has been archived by the owner on Jun 1, 2021. It is now read-only.
/
emulatetab.joelpurra.js
170 lines (133 loc) · 5.7 KB
/
emulatetab.joelpurra.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*!
* EmulateTab v0.2.4
* http://joelpurra.github.com/emulatetab
*
* Copyright © 2011, 2012, 2013 The Swedish Post and Telecom Authority (PTS)
* Developed by Joel Purra <http://joelpurra.com/>
* Released under the BSD-3-Clause license.
*
* A jQuery plugin to emulate tabbing between elements on a page.
*/
// Set up namespace, if needed
var JoelPurra = JoelPurra || {};
(function(document, $, namespace, pluginName) {
"use strict";
var eventNamespace = "." + pluginName,
// TODO: get code for :focusable, :tabbable from jQuery UI?
focusable = ":input, a[href]",
// Keep a reference to the last focused element, use as a last resort.
lastFocusedElement = null,
// Private methods
internal = {
escapeSelectorName: function(str) {
// Based on http://api.jquery.com/category/selectors/
// Still untested
return str.replace(/(!"#$%&'\(\)\*\+,\.\/:;<=>\?@\[\]^`\{\|\}~)/g, "\\\\$1");
},
findNextFocusable: function($from, offset) {
var $focusable = $(focusable)
.not(":disabled")
.not(":hidden")
.not("a[href]:empty");
if ($from[0].tagName === "INPUT" && $from[0].type === "radio" && $from[0].name !== "") {
var name = internal.escapeSelectorName($from[0].name);
$focusable = $focusable
.not("input[type=radio][name=" + name + "]")
.add($from);
}
var currentIndex = $focusable.index($from);
var nextIndex = (currentIndex + offset) % $focusable.length;
if (nextIndex <= -1) {
nextIndex = $focusable.length + nextIndex;
}
var $next = $focusable.eq(nextIndex);
return $next;
},
focusInElement: function(event) {
lastFocusedElement = event.target;
},
tryGetElementAsNonEmptyJQueryObject: function(selector) {
try {
var $element = $(selector);
if ( !! $element && $element.size() !== 0) {
return $element;
}
} catch (e) {
// Could not use element. Do nothing.
}
return null;
},
// Fix for EmulateTab Issue #2
// https://github.com/joelpurra/emulatetab/issues/2
// Combined function to get the focused element, trying as long as possible.
// Extra work done trying to avoid problems with security features around
// <input type="file" /> in Firefox (tested using 10.0.1).
// http://stackoverflow.com/questions/9301310/focus-returns-no-element-for-input-type-file-in-firefox
// Problem: http://jsfiddle.net/joelpurra/bzsv7/
// Fixed: http://jsfiddle.net/joelpurra/bzsv7/2/
getFocusedElement: function() {
// 1. Try the well-known, recommended method first.
//
// 2. Fall back to a fast method that might fail.
// Known to fail for Firefox (tested using 10.0.1) with
// Permission denied to access property "nodeType".
//
// 3. As a last resort, use the last known focused element.
// Has not been tested enough to be sure it works as expected
// in all browsers and scenarios.
//
// 4. Empty fallback
var $focused = internal.tryGetElementAsNonEmptyJQueryObject(":focus") || internal.tryGetElementAsNonEmptyJQueryObject(document.activeElement) || internal.tryGetElementAsNonEmptyJQueryObject(lastFocusedElement) || $();
return $focused;
},
emulateTabbing: function($from, offset) {
var $next = internal.findNextFocusable($from, offset);
$next.focus();
},
initializeAtLoad: function() {
// Start listener that keep track of the last focused element.
$(document)
.on("focusin" + eventNamespace, internal.focusInElement);
}
},
plugin = {
tab: function($from, offset) {
// Tab from focused element with offset, .tab(-1)
if ($.isNumeric($from)) {
offset = $from;
$from = undefined;
}
$from = $from || plugin.getFocused();
offset = offset || +1;
internal.emulateTabbing($from, offset);
},
forwardTab: function($from) {
return plugin.tab($from, +1);
},
reverseTab: function($from) {
return plugin.tab($from, -1);
},
getFocused: function() {
return internal.getFocusedElement();
}
},
installJQueryExtensions = function() {
$.extend({
emulateTab: function($from, offset) {
return plugin.tab($from, offset);
}
});
$.fn.extend({
emulateTab: function(offset) {
return plugin.tab(this, offset);
}
});
},
init = function() {
namespace[pluginName] = plugin;
installJQueryExtensions();
// EmulateTab initializes listener(s) when jQuery is ready
$(internal.initializeAtLoad);
};
init();
}(document, jQuery, JoelPurra, "EmulateTab"));