Skip to content

Instantly share code, notes, and snippets.

@garretthyder
Last active June 8, 2017 20:44
Show Gist options
  • Save garretthyder/de2e312ff8cda86ad9eb93e18d5a3f13 to your computer and use it in GitHub Desktop.
Save garretthyder/de2e312ff8cda86ad9eb93e18d5a3f13 to your computer and use it in GitHub Desktop.
Foundation Accordion script w/ fixed Deeplinking for 6.3.1
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
!function ($) {
// Custom Window Loaded Check
window.loaded = false;
$(window).load(function() {
window.loaded = true;
});
/**
* Accordion module.
* @module foundation.accordion
* @requires foundation.util.keyboard
* @requires foundation.util.motion
*/
var Accordion = function () {
/**
* Creates a new instance of an accordion.
* @class
* @fires Accordion#init
* @param {jQuery} element - jQuery object to make into an accordion.
* @param {Object} options - a plain object with settings to override the default options.
*/
function Accordion(element, options) {
_classCallCheck(this, Accordion);
this.$element = element;
this.options = $.extend({}, Accordion.defaults, this.$element.data(), options);
this._init();
Foundation.registerPlugin(this, 'Accordion');
Foundation.Keyboard.register('Accordion', {
'ENTER': 'toggle',
'SPACE': 'toggle',
'ARROW_DOWN': 'next',
'ARROW_UP': 'previous'
});
}
/**
* Initializes the accordion by animating the preset active pane(s).
* @private
*/
_createClass(Accordion, [{
key: '_init',
value: function _init() {
var _this2 = this;
this.$element.attr('role', 'tablist');
this.$tabs = this.$element.children('[data-accordion-item]');
this.$tabs.each(function (idx, el) {
var $el = $(el),
$content = $el.children('[data-tab-content]'),
id = $content[0].id || Foundation.GetYoDigits(6, 'accordion'),
linkId = el.id || id + '-label';
$el.find('a:first').attr({
'aria-controls': id,
'role': 'tab',
'id': linkId,
'aria-expanded': false,
'aria-selected': false
});
$content.attr({ 'role': 'tabpanel', 'aria-labelledby': linkId, 'aria-hidden': true, 'id': id });
});
var $initActive = this.$element.find('.is-active').children('[data-tab-content]');
this.firstTimeInit = true;
if ($initActive.length) {
this.down($initActive, this.firstTimeInit);
this.firstTimeInit = false;
}
this._checkDeepLink = function () {
var anchor = window.location.hash;
//need a hash and a relevant anchor in this tabset
if (anchor.length) {
var $target = $('.accordion-title[href$="'+anchor+'"]'),
$anchor = $(anchor);
if ($target.length && $anchor) {
// Close the right accordion stuff
if (!_this2.options.multiExpand && !_this2.firstTimeInit) {
var $activeSibling = $target.closest('[data-accordion]').find('.is-active').find('[data-tab-content]').not($anchor);
if ($activeSibling.length) {
_this2.up($activeSibling.not($anchor));
}
}
if (!$target.parent('[data-accordion-item]').hasClass('is-active')) {
_this2.down($anchor, _this2.firstTimeInit);
_this2.firstTimeInit = false;
};
//roll up a little to show the titles
if (_this2.options.deepLinkSmudge) {
var _this = _this2;
if (window.loaded) {
var offset = $target.offset();
$('html, body').animate({ scrollTop: offset.top }, _this.options.deepLinkSmudgeDelay);
} else {
$(window).load(function () {
var offset = $target.offset();
$('html, body').animate({ scrollTop: offset.top }, _this.options.deepLinkSmudgeDelay);
});
}
}
/**
* Fires when the zplugin has deeplinked at pageload
* @event Accordion#deeplink
*/
// Note: I don't believe this target is right because this.$element could be the first accordion and not the one that was deeplinked to. May need to use $target to determine $element/accordion.
_this2.$element.trigger('deeplink.zf.accordion', [$target, $anchor]);
}
}
};
//use browser to open a tab, if it exists in this tabset
if (this.options.deepLink) {
this._checkDeepLink();
}
this._events();
}
/**
* Adds event handlers for items within the accordion.
* @private
*/
}, {
key: '_events',
value: function _events() {
var _this = this;
this.$tabs.each(function () {
var $elem = $(this);
var $tabContent = $elem.children('[data-tab-content]');
if ($tabContent.length) {
$elem.children('a').off('click.zf.accordion keydown.zf.accordion').on('click.zf.accordion', function (e) {
e.preventDefault();
_this.toggle($tabContent);
}).on('keydown.zf.accordion', function (e) {
Foundation.Keyboard.handleKey(e, 'Accordion', {
toggle: function () {
_this.toggle($tabContent);
},
next: function () {
var $a = $elem.next().find('a').focus();
if (!_this.options.multiExpand) {
$a.trigger('click.zf.accordion');
}
},
previous: function () {
var $a = $elem.prev().find('a').focus();
if (!_this.options.multiExpand) {
$a.trigger('click.zf.accordion');
}
},
handled: function () {
e.preventDefault();
e.stopPropagation();
}
});
});
}
});
if (this.options.deepLink) {
$(window).on('popstate', this._checkDeepLink);
}
}
/**
* Toggles the selected content pane's open/close state.
* @param {jQuery} $target - jQuery object of the pane to toggle (`.accordion-content`).
* @function
*/
}, {
key: 'toggle',
value: function toggle($target) {
if ($target.parent().hasClass('is-active')) {
this.up($target);
} else {
this.down($target);
}
//either replace or update browser history
if (this.options.deepLink) {
var anchor = $target.prev('a').attr('href');
if (this.options.updateHistory) {
history.pushState({}, '', anchor);
} else {
history.replaceState({}, '', anchor);
}
}
}
/**
* Opens the accordion tab defined by `$target`.
* @param {jQuery} $target - Accordion pane to open (`.accordion-content`).
* @param {Boolean} firstTime - flag to determine if reflow should happen.
* @fires Accordion#down
* @function
*/
}, {
key: 'down',
value: function down($target, firstTime) {
var _this3 = this;
$target.attr('aria-hidden', false).parent('[data-tab-content]').addBack().parent().addClass('is-active');
if (!this.options.multiExpand && !firstTime) {
var $currentActive = $target.closest('[data-accordion]').find('.is-active').find('[data-tab-content]').not($target);
if ($currentActive.length) {
this.up($currentActive.not($target));
}
}
$target.slideDown(this.options.slideSpeed, function () {
/**
* Fires when the tab is done opening.
* @event Accordion#down
*/
_this3.$element.trigger('down.zf.accordion', [$target]);
});
$('#' + $target.attr('aria-labelledby')).attr({
'aria-expanded': true,
'aria-selected': true
});
}
/**
* Closes the tab defined by `$target`.
* @param {jQuery} $target - Accordion tab to close (`.accordion-content`).
* @fires Accordion#up
* @function
*/
}, {
key: 'up',
value: function up($target) {
var $aunts = $target.parent().siblings(),
_this = this;
if (!this.options.allowAllClosed && !$aunts.hasClass('is-active') || !$target.parent().hasClass('is-active')) {
return;
}
// Foundation.Move(this.options.slideSpeed, $target, function(){
$target.slideUp(_this.options.slideSpeed, function () {
/**
* Fires when the tab is done collapsing up.
* @event Accordion#up
*/
_this.$element.trigger('up.zf.accordion', [$target]);
});
// });
$target.attr('aria-hidden', true).parent().removeClass('is-active');
$('#' + $target.attr('aria-labelledby')).attr({
'aria-expanded': false,
'aria-selected': false
});
}
/**
* Destroys an instance of an accordion.
* @fires Accordion#destroyed
* @function
*/
}, {
key: 'destroy',
value: function destroy() {
this.$element.find('[data-tab-content]').stop(true).slideUp(0).css('display', '');
this.$element.find('a').off('.zf.accordion');
if (this.options.deepLink) {
$(window).off('popstate', this._checkDeepLink);
}
Foundation.unregisterPlugin(this);
}
}]);
return Accordion;
}();
Accordion.defaults = {
/**
* Amount of time to animate the opening of an accordion pane.
* @option
* @type {number}
* @default 250
*/
slideSpeed: 250,
/**
* Allow the accordion to have multiple open panes.
* @option
* @type {boolean}
* @default false
*/
multiExpand: false,
/**
* Allow the accordion to close all panes.
* @option
* @type {boolean}
* @default false
*/
allowAllClosed: false,
/**
* Allows the window to scroll to content of pane specified by hash anchor
* @option
* @type {boolean}
* @default false
*/
deepLink: false,
/**
* Adjust the deep link scroll to make sure the top of the accordion panel is visible
* @option
* @type {boolean}
* @default false
*/
deepLinkSmudge: false,
/**
* Animation time (ms) for the deep link adjustment
* @option
* @type {number}
* @default 300
*/
deepLinkSmudgeDelay: 300,
/**
* Update the browser history with the open accordion
* @option
* @type {boolean}
* @default false
*/
updateHistory: false
};
// Window exports
Foundation.plugin(Accordion, 'Accordion');
}(jQuery);
@garretthyder
Copy link
Author

I updated the gist to do a window.loaded check so we can treat links on page without window.load and treat links to the page with window.load

@garretthyder
Copy link
Author

I've replaced the script with the compiled version as it didn't previously support IE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment