353 lines
10 KiB
JavaScript
Executable File
353 lines
10 KiB
JavaScript
Executable File
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
|
|
define([
|
|
'jquery',
|
|
'jquery-ui-modules/widget',
|
|
'jquery/ui-modules/widgets/tabs',
|
|
'mage/mage',
|
|
'mage/collapsible'
|
|
], function ($) {
|
|
'use strict';
|
|
|
|
$.widget('mage.tabs', {
|
|
options: {
|
|
active: 0,
|
|
disabled: [],
|
|
openOnFocus: true,
|
|
collapsible: false,
|
|
collapsibleElement: '[data-role=collapsible]',
|
|
header: '[data-role=title]',
|
|
content: '[data-role=content]',
|
|
trigger: '[data-role=trigger]',
|
|
closedState: null,
|
|
openedState: null,
|
|
disabledState: null,
|
|
ajaxUrlElement: '[data-ajax=true]',
|
|
ajaxContent: false,
|
|
loadingClass: null,
|
|
saveState: false,
|
|
animate: false,
|
|
icons: {
|
|
activeHeader: null,
|
|
header: null
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_create: function () {
|
|
if (typeof this.options.disabled === 'string') {
|
|
this.options.disabled = this.options.disabled.split(' ').map(function (item) {
|
|
return parseInt(item, 10);
|
|
});
|
|
}
|
|
this._processPanels();
|
|
this._handleDeepLinking();
|
|
this._processTabIndex();
|
|
this._closeOthers();
|
|
this._bind();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_destroy: function () {
|
|
$.each(this.collapsibles, function () {
|
|
$(this).collapsible('destroy');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* If deep linking is used, all sections must be closed but the one that contains the anchor.
|
|
* @private
|
|
*/
|
|
_handleDeepLinking: function () {
|
|
var self = this,
|
|
anchor = window.location.hash,
|
|
isValid = $.mage.isValidSelector(anchor),
|
|
anchorId = anchor.replace('#', '');
|
|
|
|
if (anchor && isValid) {
|
|
$.each(self.contents, function (i) {
|
|
if ($(this).attr('id') === anchorId || $(this).find('#' + anchorId).length) {
|
|
self.collapsibles.not(self.collapsibles.eq(i)).collapsible('forceDeactivate');
|
|
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* When the widget gets instantiated, the first tab that is not disabled receive focusable property
|
|
* All tabs receive tabIndex 0
|
|
* @private
|
|
*/
|
|
_processTabIndex: function () {
|
|
var self = this;
|
|
|
|
self.triggers.attr('tabIndex', 0);
|
|
$.each(this.collapsibles, function (i) {
|
|
self.triggers.attr('tabIndex', 0);
|
|
self.triggers.eq(i).attr('tabIndex', 0);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Prepare the elements for instantiating the collapsible widget
|
|
* @private
|
|
*/
|
|
_processPanels: function () {
|
|
var isNotNested = this._isNotNested.bind(this);
|
|
|
|
this.contents = this.element
|
|
.find(this.options.content)
|
|
.filter(isNotNested);
|
|
|
|
this.collapsibles = this.element
|
|
.find(this.options.collapsibleElement)
|
|
.filter(isNotNested);
|
|
|
|
this.collapsibles
|
|
.attr('role', 'presentation')
|
|
.parent()
|
|
.attr('role', 'tablist');
|
|
|
|
this.headers = this.element
|
|
.find(this.options.header)
|
|
.filter(isNotNested);
|
|
|
|
if (this.headers.length === 0) {
|
|
this.headers = this.collapsibles;
|
|
}
|
|
this.triggers = this.element
|
|
.find(this.options.trigger)
|
|
.filter(isNotNested);
|
|
|
|
if (this.triggers.length === 0) {
|
|
this.triggers = this.headers;
|
|
}
|
|
this._callCollapsible();
|
|
},
|
|
|
|
/**
|
|
* Checks if element is not in nested container to keep the correct scope of collapsible
|
|
* @param {Number} index
|
|
* @param {HTMLElement} element
|
|
* @private
|
|
* @return {Boolean}
|
|
*/
|
|
_isNotNested: function (index, element) {
|
|
var parentContent = $(element).parents(this.options.content);
|
|
|
|
return !parentContent.length || !this.element.find(parentContent).length;
|
|
},
|
|
|
|
/**
|
|
* Setting the disabled and active tabs and calling instantiation of collapsible
|
|
* @private
|
|
*/
|
|
_callCollapsible: function () {
|
|
var self = this,
|
|
disabled = false,
|
|
active = false;
|
|
|
|
$.each(this.collapsibles, function (i) {
|
|
disabled = active = false;
|
|
|
|
if ($.inArray(i, self.options.disabled) !== -1) {
|
|
disabled = true;
|
|
}
|
|
|
|
if (i === self.options.active) {
|
|
active = true;
|
|
}
|
|
self._instantiateCollapsible(this, i, active, disabled);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Instantiate collapsible.
|
|
*
|
|
* @param {HTMLElement} element
|
|
* @param {Number} index
|
|
* @param {*} active
|
|
* @param {*} disabled
|
|
* @private
|
|
*/
|
|
_instantiateCollapsible: function (element, index, active, disabled) {
|
|
$(element).collapsible(
|
|
$.extend({}, this.options, {
|
|
active: active,
|
|
disabled: disabled,
|
|
header: this.headers.eq(index),
|
|
content: this.contents.eq(index),
|
|
trigger: this.triggers.eq(index)
|
|
})
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Adding callback to close others tabs when one gets opened
|
|
* @private
|
|
*/
|
|
_closeOthers: function () {
|
|
var self = this;
|
|
|
|
$.each(this.collapsibles, function () {
|
|
$(this).on('beforeOpen', function () {
|
|
self.collapsibles.not(this).collapsible('forceDeactivate');
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {*} index
|
|
*/
|
|
activate: function (index) {
|
|
this._toggleActivate('activate', index);
|
|
},
|
|
|
|
/**
|
|
* @param {*} index
|
|
*/
|
|
deactivate: function (index) {
|
|
this._toggleActivate('deactivate', index);
|
|
},
|
|
|
|
/**
|
|
* @param {*} action
|
|
* @param {*} index
|
|
* @private
|
|
*/
|
|
_toggleActivate: function (action, index) {
|
|
this.collapsibles.eq(index).collapsible(action);
|
|
},
|
|
|
|
/**
|
|
* @param {*} index
|
|
*/
|
|
disable: function (index) {
|
|
this._toggleEnable('disable', index);
|
|
},
|
|
|
|
/**
|
|
* @param {*} index
|
|
*/
|
|
enable: function (index) {
|
|
this._toggleEnable('enable', index);
|
|
},
|
|
|
|
/**
|
|
* @param {*} action
|
|
* @param {*} index
|
|
* @private
|
|
*/
|
|
_toggleEnable: function (action, index) {
|
|
var self = this;
|
|
|
|
if (Array.isArray(index)) {
|
|
$.each(index, function () {
|
|
self.collapsibles.eq(this).collapsible(action);
|
|
});
|
|
} else if (index === undefined) {
|
|
this.collapsibles.collapsible(action);
|
|
} else {
|
|
this.collapsibles.eq(index).collapsible(action);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {jQuery.Event} event
|
|
* @private
|
|
*/
|
|
_keydown: function (event) {
|
|
var self = this,
|
|
keyCode, toFocus, toFocusIndex, enabledTriggers, length, currentIndex, nextToFocus;
|
|
|
|
if (event.altKey || event.ctrlKey) {
|
|
return;
|
|
}
|
|
keyCode = $.ui.keyCode;
|
|
toFocus = false;
|
|
enabledTriggers = [];
|
|
|
|
$.each(this.triggers, function () {
|
|
if (!self.collapsibles.eq(self.triggers.index($(this))).collapsible('option', 'disabled')) {
|
|
enabledTriggers.push(this);
|
|
}
|
|
});
|
|
length = $(enabledTriggers).length;
|
|
currentIndex = $(enabledTriggers).index(event.target);
|
|
|
|
/**
|
|
* @param {String} direction
|
|
* @return {*}
|
|
*/
|
|
nextToFocus = function (direction) {
|
|
if (length > 0) {
|
|
if (direction === 'right') {
|
|
toFocusIndex = (currentIndex + 1) % length;
|
|
} else {
|
|
toFocusIndex = (currentIndex + length - 1) % length;
|
|
}
|
|
|
|
return enabledTriggers[toFocusIndex];
|
|
}
|
|
|
|
return event.target;
|
|
};
|
|
|
|
switch (event.keyCode) {
|
|
case keyCode.RIGHT:
|
|
case keyCode.DOWN:
|
|
toFocus = nextToFocus('right');
|
|
break;
|
|
|
|
case keyCode.LEFT:
|
|
case keyCode.UP:
|
|
toFocus = nextToFocus('left');
|
|
break;
|
|
|
|
case keyCode.HOME:
|
|
toFocus = enabledTriggers[0];
|
|
break;
|
|
|
|
case keyCode.END:
|
|
toFocus = enabledTriggers[length - 1];
|
|
break;
|
|
}
|
|
|
|
if (toFocus) {
|
|
toFocusIndex = this.triggers.index(toFocus);
|
|
$(event.target).attr('tabIndex', -1);
|
|
$(toFocus).attr('tabIndex', 0);
|
|
toFocus.focus();
|
|
|
|
if (this.options.openOnFocus) {
|
|
this.activate(toFocusIndex);
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_bind: function () {
|
|
var events = {
|
|
keydown: '_keydown'
|
|
};
|
|
|
|
this._off(this.triggers);
|
|
this._on(this.triggers, events);
|
|
}
|
|
});
|
|
|
|
return $.mage.tabs;
|
|
});
|