infojune/resources/sass/materialize/js/tabs.js

403 lines
10 KiB
JavaScript

(function($, anim) {
'use strict';
let _defaults = {
duration: 300,
onShow: null,
swipeable: false,
responsiveThreshold: Infinity // breakpoint for swipeable
};
/**
* @class
*
*/
class Tabs extends Component {
/**
* Construct Tabs instance
* @constructor
* @param {Element} el
* @param {Object} options
*/
constructor(el, options) {
super(Tabs, el, options);
this.el.M_Tabs = this;
/**
* Options for the Tabs
* @member Tabs#options
* @prop {Number} duration
* @prop {Function} onShow
* @prop {Boolean} swipeable
* @prop {Number} responsiveThreshold
*/
this.options = $.extend({}, Tabs.defaults, options);
// Setup
this.$tabLinks = this.$el.children('li.tab').children('a');
this.index = 0;
this._setupActiveTabLink();
// Setup tabs content
if (this.options.swipeable) {
this._setupSwipeableTabs();
} else {
this._setupNormalTabs();
}
// Setup tabs indicator after content to ensure accurate widths
this._setTabsAndTabWidth();
this._createIndicator();
this._setupEventHandlers();
}
static get defaults() {
return _defaults;
}
static init(els, options) {
return super.init(this, els, options);
}
/**
* Get Instance
*/
static getInstance(el) {
let domElem = !!el.jquery ? el[0] : el;
return domElem.M_Tabs;
}
/**
* Teardown component
*/
destroy() {
this._removeEventHandlers();
this._indicator.parentNode.removeChild(this._indicator);
if (this.options.swipeable) {
this._teardownSwipeableTabs();
} else {
this._teardownNormalTabs();
}
this.$el[0].M_Tabs = undefined;
}
/**
* Setup Event Handlers
*/
_setupEventHandlers() {
this._handleWindowResizeBound = this._handleWindowResize.bind(this);
window.addEventListener('resize', this._handleWindowResizeBound);
this._handleTabClickBound = this._handleTabClick.bind(this);
this.el.addEventListener('click', this._handleTabClickBound);
}
/**
* Remove Event Handlers
*/
_removeEventHandlers() {
window.removeEventListener('resize', this._handleWindowResizeBound);
this.el.removeEventListener('click', this._handleTabClickBound);
}
/**
* Handle window Resize
*/
_handleWindowResize() {
this._setTabsAndTabWidth();
if (this.tabWidth !== 0 && this.tabsWidth !== 0) {
this._indicator.style.left = this._calcLeftPos(this.$activeTabLink) + 'px';
this._indicator.style.right = this._calcRightPos(this.$activeTabLink) + 'px';
}
}
/**
* Handle tab click
* @param {Event} e
*/
_handleTabClick(e) {
let tab = $(e.target).closest('li.tab');
let tabLink = $(e.target).closest('a');
// Handle click on tab link only
if (!tabLink.length || !tabLink.parent().hasClass('tab')) {
return;
}
if (tab.hasClass('disabled')) {
e.preventDefault();
return;
}
// Act as regular link if target attribute is specified.
if (!!tabLink.attr('target')) {
return;
}
// Make the old tab inactive.
this.$activeTabLink.removeClass('active');
let $oldContent = this.$content;
// Update the variables with the new link and content
this.$activeTabLink = tabLink;
this.$content = $(M.escapeHash(tabLink[0].hash));
this.$tabLinks = this.$el.children('li.tab').children('a');
// Make the tab active.
this.$activeTabLink.addClass('active');
let prevIndex = this.index;
this.index = Math.max(this.$tabLinks.index(tabLink), 0);
// Swap content
if (this.options.swipeable) {
if (this._tabsCarousel) {
this._tabsCarousel.set(this.index, () => {
if (typeof this.options.onShow === 'function') {
this.options.onShow.call(this, this.$content[0]);
}
});
}
} else {
if (this.$content.length) {
this.$content[0].style.display = 'block';
this.$content.addClass('active');
if (typeof this.options.onShow === 'function') {
this.options.onShow.call(this, this.$content[0]);
}
if ($oldContent.length && !$oldContent.is(this.$content)) {
$oldContent[0].style.display = 'none';
$oldContent.removeClass('active');
}
}
}
// Update widths after content is swapped (scrollbar bugfix)
this._setTabsAndTabWidth();
// Update indicator
this._animateIndicator(prevIndex);
// Prevent the anchor's default click action
e.preventDefault();
}
/**
* Generate elements for tab indicator.
*/
_createIndicator() {
let indicator = document.createElement('li');
indicator.classList.add('indicator');
this.el.appendChild(indicator);
this._indicator = indicator;
setTimeout(() => {
this._indicator.style.left = this._calcLeftPos(this.$activeTabLink) + 'px';
this._indicator.style.right = this._calcRightPos(this.$activeTabLink) + 'px';
}, 0);
}
/**
* Setup first active tab link.
*/
_setupActiveTabLink() {
// If the location.hash matches one of the links, use that as the active tab.
this.$activeTabLink = $(this.$tabLinks.filter('[href="' + location.hash + '"]'));
// If no match is found, use the first link or any with class 'active' as the initial active tab.
if (this.$activeTabLink.length === 0) {
this.$activeTabLink = this.$el
.children('li.tab')
.children('a.active')
.first();
}
if (this.$activeTabLink.length === 0) {
this.$activeTabLink = this.$el
.children('li.tab')
.children('a')
.first();
}
this.$tabLinks.removeClass('active');
this.$activeTabLink[0].classList.add('active');
this.index = Math.max(this.$tabLinks.index(this.$activeTabLink), 0);
if (this.$activeTabLink.length) {
this.$content = $(M.escapeHash(this.$activeTabLink[0].hash));
this.$content.addClass('active');
}
}
/**
* Setup swipeable tabs
*/
_setupSwipeableTabs() {
// Change swipeable according to responsive threshold
if (window.innerWidth > this.options.responsiveThreshold) {
this.options.swipeable = false;
}
let $tabsContent = $();
this.$tabLinks.each((link) => {
let $currContent = $(M.escapeHash(link.hash));
$currContent.addClass('carousel-item');
$tabsContent = $tabsContent.add($currContent);
});
let $tabsWrapper = $('<div class="tabs-content carousel carousel-slider"></div>');
$tabsContent.first().before($tabsWrapper);
$tabsWrapper.append($tabsContent);
$tabsContent[0].style.display = '';
// Keep active tab index to set initial carousel slide
let activeTabIndex = this.$activeTabLink.closest('.tab').index();
this._tabsCarousel = M.Carousel.init($tabsWrapper[0], {
fullWidth: true,
noWrap: true,
onCycleTo: (item) => {
let prevIndex = this.index;
this.index = $(item).index();
this.$activeTabLink.removeClass('active');
this.$activeTabLink = this.$tabLinks.eq(this.index);
this.$activeTabLink.addClass('active');
this._animateIndicator(prevIndex);
if (typeof this.options.onShow === 'function') {
this.options.onShow.call(this, this.$content[0]);
}
}
});
// Set initial carousel slide to active tab
this._tabsCarousel.set(activeTabIndex);
}
/**
* Teardown normal tabs.
*/
_teardownSwipeableTabs() {
let $tabsWrapper = this._tabsCarousel.$el;
this._tabsCarousel.destroy();
// Unwrap
$tabsWrapper.after($tabsWrapper.children());
$tabsWrapper.remove();
}
/**
* Setup normal tabs.
*/
_setupNormalTabs() {
// Hide Tabs Content
this.$tabLinks.not(this.$activeTabLink).each((link) => {
if (!!link.hash) {
let $currContent = $(M.escapeHash(link.hash));
if ($currContent.length) {
$currContent[0].style.display = 'none';
}
}
});
}
/**
* Teardown normal tabs.
*/
_teardownNormalTabs() {
// show Tabs Content
this.$tabLinks.each((link) => {
if (!!link.hash) {
let $currContent = $(M.escapeHash(link.hash));
if ($currContent.length) {
$currContent[0].style.display = '';
}
}
});
}
/**
* set tabs and tab width
*/
_setTabsAndTabWidth() {
this.tabsWidth = this.$el.width();
this.tabWidth = Math.max(this.tabsWidth, this.el.scrollWidth) / this.$tabLinks.length;
}
/**
* Finds right attribute for indicator based on active tab.
* @param {cash} el
*/
_calcRightPos(el) {
return Math.ceil(this.tabsWidth - el.position().left - el[0].getBoundingClientRect().width);
}
/**
* Finds left attribute for indicator based on active tab.
* @param {cash} el
*/
_calcLeftPos(el) {
return Math.floor(el.position().left);
}
updateTabIndicator() {
this._setTabsAndTabWidth();
this._animateIndicator(this.index);
}
/**
* Animates Indicator to active tab.
* @param {Number} prevIndex
*/
_animateIndicator(prevIndex) {
let leftDelay = 0,
rightDelay = 0;
if (this.index - prevIndex >= 0) {
leftDelay = 90;
} else {
rightDelay = 90;
}
// Animate
let animOptions = {
targets: this._indicator,
left: {
value: this._calcLeftPos(this.$activeTabLink),
delay: leftDelay
},
right: {
value: this._calcRightPos(this.$activeTabLink),
delay: rightDelay
},
duration: this.options.duration,
easing: 'easeOutQuad'
};
anim.remove(this._indicator);
anim(animOptions);
}
/**
* Select tab.
* @param {String} tabId
*/
select(tabId) {
let tab = this.$tabLinks.filter('[href="#' + tabId + '"]');
if (tab.length) {
tab.trigger('click');
}
}
}
M.Tabs = Tabs;
if (M.jQueryLoaded) {
M.initializeJqueryWrapper(Tabs, 'tabs', 'M_Tabs');
}
})(cash, M.anime);