Skip to content

Commit 296bd17

Browse files
committed
Use pull request foundation#11477 from ncoden/fix/initial-tab-deep-linking-11100 for v6.5.0
5ea51f3 fix: return to initial state when reseting hash in Accordion & Tabs foundation#11100 314b4ee fix: fix initial state for Accordion without any panel active abbbdbf refactor: split DOM operations from logics in Accordion e261905 refactor: move "init only" Accordion logic to its own methods b819faf docs: remove outdated comments in Accordion related to the initial state 7307c43 feat: add "Tabs._collapse()" to collapse the active tab 012db10 refactor: clean up the Tab initialization c6d37c4 fix: close the active Tab when going back to a "tab-less" history state 2673567 docs: fix "zplugin" typo in Accordion & Tabs events Signed-off-by: Nicolas Coden <[email protected]>
1 parent aa0fb5c commit 296bd17

File tree

2 files changed

+191
-102
lines changed

2 files changed

+191
-102
lines changed

js/foundation.accordion.js

+131-72
Original file line numberDiff line numberDiff line change
@@ -59,41 +59,50 @@ class Accordion extends Plugin {
5959

6060
$content.attr({'role': 'tabpanel', 'aria-labelledby': linkId, 'aria-hidden': true, 'id': id});
6161
});
62+
6263
var $initActive = this.$element.find('.is-active').children('[data-tab-content]');
63-
this.firstTimeInit = true;
64-
if($initActive.length){
65-
this.down($initActive, this.firstTimeInit);
66-
this.firstTimeInit = false;
64+
if ($initActive.length) {
65+
// Save up the initial hash to return to it later when going back in history
66+
this._initialAnchor = $initActive.prev('a').attr('href');
67+
this._openSingleTab($initActive);
6768
}
6869

6970
this._checkDeepLink = () => {
7071
var anchor = window.location.hash;
71-
//need a hash and a relevant anchor in this tabset
72-
if(anchor.length) {
73-
var $link = this.$element.find('[href$="'+anchor+'"]'),
74-
$anchor = $(anchor);
75-
76-
if ($link.length && $anchor) {
77-
if (!$link.parent('[data-accordion-item]').hasClass('is-active')) {
78-
this.down($anchor, this.firstTimeInit);
79-
this.firstTimeInit = false;
80-
};
81-
82-
//roll up a little to show the titles
83-
if (this.options.deepLinkSmudge) {
84-
var _this = this;
85-
onLoad($(window), function() {
86-
var offset = _this.$element.offset();
87-
$('html, body').animate({ scrollTop: offset.top }, _this.options.deepLinkSmudgeDelay);
88-
});
89-
}
90-
91-
/**
92-
* Fires when the zplugin has deeplinked at pageload
93-
* @event Accordion#deeplink
94-
*/
95-
this.$element.trigger('deeplink.zf.accordion', [$link, $anchor]);
96-
}
72+
73+
// If there is no anchor, return to the initial panel
74+
if (!anchor.length && this._initialAnchor) {
75+
anchor = this._initialAnchor;
76+
}
77+
78+
var $anchor = anchor && $(anchor);
79+
var $link = anchor && this.$element.find(`[href$="${anchor}"]`);
80+
81+
// If there is an anchor for the hash, open it (if not already active)
82+
if ($anchor && $link && $link.length) {
83+
if (!$link.parent('[data-accordion-item]').hasClass('is-active')) {
84+
this._openSingleTab($anchor);
85+
};
86+
}
87+
// Otherwise, close everything
88+
else {
89+
this._closeAllTabs();
90+
}
91+
92+
// Roll up a little to show the titles
93+
if (this.options.deepLinkSmudge) {
94+
onLoad($(window), () => {
95+
var offset = this.$element.offset();
96+
$('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
97+
});
98+
}
99+
100+
if ($anchor && $link) {
101+
/**
102+
* Fires when the plugin has deeplinked at pageload
103+
* @event Accordion#deeplink
104+
*/
105+
this.$element.trigger('deeplink.zf.accordion', [$link, $anchor]);
97106
}
98107
}
99108

@@ -180,80 +189,130 @@ class Accordion extends Plugin {
180189
/**
181190
* Opens the accordion tab defined by `$target`.
182191
* @param {jQuery} $target - Accordion pane to open (`.accordion-content`).
183-
* @param {Boolean} firstTime - flag to determine if reflow should happen.
184192
* @fires Accordion#down
185193
* @function
186194
*/
187-
down($target, firstTime) {
188-
/**
189-
* checking firstTime allows for initial render of the accordion
190-
* to render preset is-active panes.
191-
*/
192-
if ($target.closest('[data-accordion]').is('[disabled]') && !firstTime) {
195+
down($target) {
196+
if ($target.closest('[data-accordion]').is('[disabled]')) {
193197
console.info('Cannot call down on an accordion that is disabled.');
194198
return;
195199
}
196-
$target
197-
.attr('aria-hidden', false)
198-
.parent('[data-tab-content]')
199-
.addBack()
200-
.parent().addClass('is-active');
201-
202-
if (!this.options.multiExpand && !firstTime) {
203-
var $currentActive = this.$element.children('.is-active').children('[data-tab-content]');
204-
if ($currentActive.length) {
205-
this.up($currentActive.not($target));
206-
}
200+
201+
if (this.options.multiExpand)
202+
this._openTab($target);
203+
else
204+
this._openSingleTab($target);
205+
}
206+
207+
/**
208+
* Closes the tab defined by `$target`.
209+
* It may be ignored if the Accordion options don't allow it.
210+
*
211+
* @param {jQuery} $target - Accordion tab to close (`.accordion-content`).
212+
* @fires Accordion#up
213+
* @function
214+
*/
215+
up($target) {
216+
if (this.$element.is('[disabled]')) {
217+
console.info('Cannot call up on an accordion that is disabled.');
218+
return;
219+
}
220+
221+
// Don't close the item if it is already closed
222+
const $targetItem = $target.parent();
223+
if (!$targetItem.hasClass('is-active')) return;
224+
225+
// Don't close the item if there is no other active item (unless with `allowAllClosed`)
226+
const $othersItems = $targetItem.siblings();
227+
if (!this.options.allowAllClosed && !$othersItems.hasClass('is-active')) return;
228+
229+
this._closeTab($target);
230+
}
231+
232+
/**
233+
* Make the tab defined by `$target` the only opened tab, closing all others tabs.
234+
* @param {jQuery} $target - Accordion tab to open (`.accordion-content`).
235+
* @function
236+
* @private
237+
*/
238+
_openSingleTab($target) {
239+
// Close all the others active tabs.
240+
const $activeContents = this.$element.children('.is-active').children('[data-tab-content]');
241+
if ($activeContents.length) {
242+
this._closeTab($activeContents.not($target));
207243
}
208244

245+
// Then open the target.
246+
this._openTab($target);
247+
}
248+
249+
/**
250+
* Opens the tab defined by `$target`.
251+
* @param {jQuery} $target - Accordion tab to open (`.accordion-content`).
252+
* @fires Accordion#down
253+
* @function
254+
* @private
255+
*/
256+
_openTab($target) {
257+
const $targetItem = $target.parent();
258+
const targetContentId = $target.attr('aria-labelledby');
259+
260+
$target.attr('aria-hidden', false);
261+
$targetItem.addClass('is-active');
262+
263+
$(`#${targetContentId}`).attr({
264+
'aria-expanded': true,
265+
'aria-selected': true
266+
});
267+
209268
$target.slideDown(this.options.slideSpeed, () => {
210269
/**
211270
* Fires when the tab is done opening.
212271
* @event Accordion#down
213272
*/
214273
this.$element.trigger('down.zf.accordion', [$target]);
215274
});
216-
217-
$(`#${$target.attr('aria-labelledby')}`).attr({
218-
'aria-expanded': true,
219-
'aria-selected': true
220-
});
221275
}
222276

223277
/**
224278
* Closes the tab defined by `$target`.
225279
* @param {jQuery} $target - Accordion tab to close (`.accordion-content`).
226280
* @fires Accordion#up
227281
* @function
282+
* @private
228283
*/
229-
up($target) {
230-
if ($target.closest('[data-accordion]').is('[disabled]')) {
231-
console.info('Cannot call up on an accordion that is disabled.');
232-
return;
233-
}
284+
_closeTab($target) {
285+
const $targetItem = $target.parent();
286+
const targetContentId = $target.attr('aria-labelledby');
234287

235-
var $aunts = $target.parent().siblings(),
236-
_this = this;
288+
$target.attr('aria-hidden', true)
289+
$targetItem.removeClass('is-active');
237290

238-
if((!this.options.allowAllClosed && !$aunts.hasClass('is-active')) || !$target.parent().hasClass('is-active')) {
239-
return;
240-
}
291+
$(`#${targetContentId}`).attr({
292+
'aria-expanded': false,
293+
'aria-selected': false
294+
});
241295

242-
$target.slideUp(_this.options.slideSpeed, function () {
296+
$target.slideUp(this.options.slideSpeed, () => {
243297
/**
244298
* Fires when the tab is done collapsing up.
245299
* @event Accordion#up
246300
*/
247-
_this.$element.trigger('up.zf.accordion', [$target]);
301+
this.$element.trigger('up.zf.accordion', [$target]);
248302
});
303+
}
249304

250-
$target.attr('aria-hidden', true)
251-
.parent().removeClass('is-active');
252-
253-
$(`#${$target.attr('aria-labelledby')}`).attr({
254-
'aria-expanded': false,
255-
'aria-selected': false
256-
});
305+
/**
306+
* Closes all active tabs
307+
* @fires Accordion#up
308+
* @function
309+
* @private
310+
*/
311+
_closeAllTabs() {
312+
var $activeTabs = this.$element.children('.is-active').children('[data-tab-content]');
313+
if ($activeTabs.length) {
314+
this._closeTab($activeTabs);
315+
}
257316
}
258317

259318
/**

js/foundation.tabs.js

+60-30
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ class Tabs extends Plugin {
7373
'aria-labelledby': linkId
7474
});
7575

76+
// Save up the initial hash to return to it later when going back in history
77+
if (isActive) {
78+
_this._initialAnchor = `#${hash}`;
79+
}
80+
7681
if(!isActive) {
7782
$tabContent.attr('aria-hidden', 'true');
7883
}
@@ -85,6 +90,7 @@ class Tabs extends Plugin {
8590
});
8691
}
8792
});
93+
8894
if(this.options.matchHeight) {
8995
var $images = this.$tabContent.find('img');
9096

@@ -95,29 +101,41 @@ class Tabs extends Plugin {
95101
}
96102
}
97103

98-
//current context-bound function to open tabs on page load or history hashchange
104+
// Current context-bound function to open tabs on page load or history hashchange
99105
this._checkDeepLink = () => {
100106
var anchor = window.location.hash;
101-
//need a hash and a relevant anchor in this tabset
102-
if(anchor.length) {
103-
var $link = this.$element.find('[href$="'+anchor+'"]');
104-
if ($link.length) {
105-
this.selectTab($(anchor), true);
106-
107-
//roll up a little to show the titles
108-
if (this.options.deepLinkSmudge) {
109-
var offset = this.$element.offset();
110-
$('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
111-
}
112107

113-
/**
114-
* Fires when the zplugin has deeplinked at pageload
115-
* @event Tabs#deeplink
116-
*/
117-
this.$element.trigger('deeplink.zf.tabs', [$link, $(anchor)]);
118-
}
119-
}
120-
}
108+
// If there is no anchor, return to the initial panel
109+
if (!anchor.length && this._initialAnchor) {
110+
anchor = this._initialAnchor;
111+
}
112+
113+
var $anchor = anchor && $(anchor);
114+
var $link = anchor && this.$element.find('[href$="'+anchor+'"]');
115+
116+
// If there is an anchor for the hash, select it
117+
if ($anchor && $anchor.length && $link && $link.length) {
118+
this.selectTab($anchor, true);
119+
}
120+
// Otherwise, collapse everything
121+
else {
122+
this._collapse();
123+
}
124+
125+
// Roll up a little to show the titles
126+
if (this.options.deepLinkSmudge) {
127+
var offset = this.$element.offset();
128+
$('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
129+
}
130+
131+
if ($anchor && $link) {
132+
/**
133+
* Fires when the plugin has deeplinked at pageload
134+
* @event Tabs#deeplink
135+
*/
136+
this.$element.trigger('deeplink.zf.tabs', [$link, $anchor]);
137+
}
138+
}
121139

122140
//use browser to open a tab, if it exists in this tabset
123141
if (this.options.deepLink) {
@@ -223,18 +241,10 @@ class Tabs extends Plugin {
223241
*/
224242
_handleTabChange($target, historyHandled) {
225243

226-
/**
227-
* Check for active class on target. Collapse if exists.
228-
*/
244+
// With `activeCollapse`, if the target is the active Tab, collapse it.
229245
if ($target.hasClass(`${this.options.linkActiveClass}`)) {
230246
if(this.options.activeCollapse) {
231-
this._collapseTab($target);
232-
233-
/**
234-
* Fires when the zplugin has successfully collapsed tabs.
235-
* @event Tabs#collapse
236-
*/
237-
this.$element.trigger('collapse.zf.tabs', [$target]);
247+
this._collapse();
238248
}
239249
return;
240250
}
@@ -310,6 +320,25 @@ class Tabs extends Plugin {
310320
.attr({ 'aria-hidden': 'true' })
311321
}
312322

323+
/**
324+
* Collapses the active Tab.
325+
* @fires Tabs#collapse
326+
* @function
327+
*/
328+
_collapse() {
329+
var $activeTab = this.$element.find(`.${this.options.linkClass}.${this.options.linkActiveClass}`);
330+
331+
if ($activeTab.length) {
332+
this._collapseTab($activeTab);
333+
334+
/**
335+
* Fires when the plugin has successfully collapsed tabs.
336+
* @event Tabs#collapse
337+
*/
338+
this.$element.trigger('collapse.zf.tabs', [$activeTab]);
339+
}
340+
}
341+
313342
/**
314343
* Public method for selecting a content pane to display.
315344
* @param {jQuery | String} elem - jQuery object or string of the id of the pane to display.
@@ -333,6 +362,7 @@ class Tabs extends Plugin {
333362

334363
this._handleTabChange($target, historyHandled);
335364
};
365+
336366
/**
337367
* Sets the height of each panel to the height of the tallest panel.
338368
* If enabled in options, gets called on media query change.

0 commit comments

Comments
 (0)