comparison js/foundation/foundation.tab.js @ 0:7abe02bf29ec

initial commit
author Alex Krolick <whokilledtheelectricmonk@gmail.com>
date Sat, 07 Nov 2015 18:04:42 -0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:7abe02bf29ec
1 ;(function ($, window, document, undefined) {
2 'use strict';
3
4 Foundation.libs.tab = {
5 name : 'tab',
6
7 version : '5.5.3',
8
9 settings : {
10 active_class : 'active',
11 callback : function () {},
12 deep_linking : false,
13 scroll_to_content : true,
14 is_hover : false
15 },
16
17 default_tab_hashes : [],
18
19 init : function (scope, method, options) {
20 var self = this,
21 S = this.S;
22
23 // Store the default active tabs which will be referenced when the
24 // location hash is absent, as in the case of navigating the tabs and
25 // returning to the first viewing via the browser Back button.
26 S('[' + this.attr_name() + '] > .active > a', this.scope).each(function () {
27 self.default_tab_hashes.push(this.hash);
28 });
29
30 this.bindings(method, options);
31 this.handle_location_hash_change();
32 },
33
34 events : function () {
35 var self = this,
36 S = this.S;
37
38 var usual_tab_behavior = function (e, target) {
39 var settings = S(target).closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init');
40 if (!settings.is_hover || Modernizr.touch) {
41 // if user did not pressed tab key, prevent default action
42 var keyCode = e.keyCode || e.which;
43 if (keyCode !== 9) {
44 e.preventDefault();
45 e.stopPropagation();
46 }
47 self.toggle_active_tab(S(target).parent());
48
49 }
50 };
51
52 S(this.scope)
53 .off('.tab')
54 // Key event: focus/tab key
55 .on('keydown.fndtn.tab', '[' + this.attr_name() + '] > * > a', function(e) {
56 var keyCode = e.keyCode || e.which;
57 // if user pressed tab key
58 if (keyCode === 13 || keyCode === 32) { // enter or space
59 var el = this;
60 usual_tab_behavior(e, el);
61 }
62 })
63 // Click event: tab title
64 .on('click.fndtn.tab', '[' + this.attr_name() + '] > * > a', function(e) {
65 var el = this;
66 usual_tab_behavior(e, el);
67 })
68 // Hover event: tab title
69 .on('mouseenter.fndtn.tab', '[' + this.attr_name() + '] > * > a', function (e) {
70 var settings = S(this).closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init');
71 if (settings.is_hover) {
72 self.toggle_active_tab(S(this).parent());
73 }
74 });
75
76 // Location hash change event
77 S(window).on('hashchange.fndtn.tab', function (e) {
78 e.preventDefault();
79 self.handle_location_hash_change();
80 });
81 },
82
83 handle_location_hash_change : function () {
84
85 var self = this,
86 S = this.S;
87
88 S('[' + this.attr_name() + ']', this.scope).each(function () {
89 var settings = S(this).data(self.attr_name(true) + '-init');
90 if (settings.deep_linking) {
91 // Match the location hash to a label
92 var hash;
93 if (settings.scroll_to_content) {
94 hash = self.scope.location.hash;
95 } else {
96 // prefix the hash to prevent anchor scrolling
97 hash = self.scope.location.hash.replace('fndtn-', '');
98 }
99 if (hash != '') {
100 // Check whether the location hash references a tab content div or
101 // another element on the page (inside or outside the tab content div)
102 var hash_element = S(hash);
103 if (hash_element.hasClass('content') && hash_element.parent().hasClass('tabs-content')) {
104 // Tab content div
105 self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=' + hash + ']').parent());
106 } else {
107 // Not the tab content div. If inside the tab content, find the
108 // containing tab and toggle it as active.
109 var hash_tab_container_id = hash_element.closest('.content').attr('id');
110 if (hash_tab_container_id != undefined) {
111 self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=#' + hash_tab_container_id + ']').parent(), hash);
112 }
113 }
114 } else {
115 // Reference the default tab hashes which were initialized in the init function
116 for (var ind = 0; ind < self.default_tab_hashes.length; ind++) {
117 self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=' + self.default_tab_hashes[ind] + ']').parent());
118 }
119 }
120 }
121 });
122 },
123
124 toggle_active_tab : function (tab, location_hash) {
125 var self = this,
126 S = self.S,
127 tabs = tab.closest('[' + this.attr_name() + ']'),
128 tab_link = tab.find('a'),
129 anchor = tab.children('a').first(),
130 target_hash = '#' + anchor.attr('href').split('#')[1],
131 target = S(target_hash),
132 siblings = tab.siblings(),
133 settings = tabs.data(this.attr_name(true) + '-init'),
134 interpret_keyup_action = function (e) {
135 // Light modification of Heydon Pickering's Practical ARIA Examples: http://heydonworks.com/practical_aria_examples/js/a11y.js
136
137 // define current, previous and next (possible) tabs
138
139 var $original = $(this);
140 var $prev = $(this).parents('li').prev().children('[role="tab"]');
141 var $next = $(this).parents('li').next().children('[role="tab"]');
142 var $target;
143
144 // find the direction (prev or next)
145
146 switch (e.keyCode) {
147 case 37:
148 $target = $prev;
149 break;
150 case 39:
151 $target = $next;
152 break;
153 default:
154 $target = false
155 break;
156 }
157
158 if ($target.length) {
159 $original.attr({
160 'tabindex' : '-1',
161 'aria-selected' : null
162 });
163 $target.attr({
164 'tabindex' : '0',
165 'aria-selected' : true
166 }).focus();
167 }
168
169 // Hide panels
170
171 $('[role="tabpanel"]')
172 .attr('aria-hidden', 'true');
173
174 // Show panel which corresponds to target
175
176 $('#' + $(document.activeElement).attr('href').substring(1))
177 .attr('aria-hidden', null);
178
179 },
180 go_to_hash = function(hash) {
181 // This function allows correct behaviour of the browser's back button when deep linking is enabled. Without it
182 // the user would get continually redirected to the default hash.
183 var default_hash = settings.scroll_to_content ? self.default_tab_hashes[0] : 'fndtn-' + self.default_tab_hashes[0].replace('#', '');
184
185 if (hash !== default_hash || window.location.hash) {
186 window.location.hash = hash;
187 }
188 };
189
190 // allow usage of data-tab-content attribute instead of href
191 if (anchor.data('tab-content')) {
192 target_hash = '#' + anchor.data('tab-content').split('#')[1];
193 target = S(target_hash);
194 }
195
196 if (settings.deep_linking) {
197
198 if (settings.scroll_to_content) {
199
200 // retain current hash to scroll to content
201 go_to_hash(location_hash || target_hash);
202
203 if (location_hash == undefined || location_hash == target_hash) {
204 tab.parent()[0].scrollIntoView();
205 } else {
206 S(target_hash)[0].scrollIntoView();
207 }
208 } else {
209 // prefix the hashes so that the browser doesn't scroll down
210 if (location_hash != undefined) {
211 go_to_hash('fndtn-' + location_hash.replace('#', ''));
212 } else {
213 go_to_hash('fndtn-' + target_hash.replace('#', ''));
214 }
215 }
216 }
217
218 // WARNING: The activation and deactivation of the tab content must
219 // occur after the deep linking in order to properly refresh the browser
220 // window (notably in Chrome).
221 // Clean up multiple attr instances to done once
222 tab.addClass(settings.active_class).triggerHandler('opened');
223 tab_link.attr({'aria-selected' : 'true', tabindex : 0});
224 siblings.removeClass(settings.active_class)
225 siblings.find('a').attr({'aria-selected' : 'false'/*, tabindex : -1*/});
226 target.siblings().removeClass(settings.active_class).attr({'aria-hidden' : 'true'/*, tabindex : -1*/});
227 target.addClass(settings.active_class).attr('aria-hidden', 'false').removeAttr('tabindex');
228 settings.callback(tab);
229 target.triggerHandler('toggled', [target]);
230 tabs.triggerHandler('toggled', [tab]);
231
232 tab_link.off('keydown').on('keydown', interpret_keyup_action );
233 },
234
235 data_attr : function (str) {
236 if (this.namespace.length > 0) {
237 return this.namespace + '-' + str;
238 }
239
240 return str;
241 },
242
243 off : function () {},
244
245 reflow : function () {}
246 };
247 }(jQuery, window, window.document));