tabs.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. define([
  6. 'jquery',
  7. 'jquery/ui',
  8. 'mage/mage',
  9. 'mage/collapsible'
  10. ], function ($) {
  11. 'use strict';
  12. $.widget('mage.tabs', {
  13. options: {
  14. active: 0,
  15. disabled: [],
  16. openOnFocus: true,
  17. collapsible: false,
  18. collapsibleElement: '[data-role=collapsible]',
  19. header: '[data-role=title]',
  20. content: '[data-role=content]',
  21. trigger: '[data-role=trigger]',
  22. closedState: null,
  23. openedState: null,
  24. disabledState: null,
  25. ajaxUrlElement: '[data-ajax=true]',
  26. ajaxContent: false,
  27. loadingClass: null,
  28. saveState: false,
  29. animate: false,
  30. icons: {
  31. activeHeader: null,
  32. header: null
  33. }
  34. },
  35. /**
  36. * @private
  37. */
  38. _create: function () {
  39. if (typeof this.options.disabled === 'string') {
  40. this.options.disabled = this.options.disabled.split(' ').map(function (item) {
  41. return parseInt(item, 10);
  42. });
  43. }
  44. this._processPanels();
  45. this._handleDeepLinking();
  46. this._processTabIndex();
  47. this._closeOthers();
  48. this._bind();
  49. },
  50. /**
  51. * @private
  52. */
  53. _destroy: function () {
  54. $.each(this.collapsibles, function () {
  55. $(this).collapsible('destroy');
  56. });
  57. },
  58. /**
  59. * If deep linking is used, all sections must be closed but the one that contains the anchor.
  60. * @private
  61. */
  62. _handleDeepLinking: function () {
  63. var self = this,
  64. anchor = window.location.hash,
  65. isValid = $.mage.isValidSelector(anchor),
  66. anchorId = anchor.replace('#', '');
  67. if (anchor && isValid) {
  68. $.each(self.contents, function (i) {
  69. if ($(this).attr('id') === anchorId) {
  70. self.collapsibles.not(self.collapsibles.eq(i)).collapsible('forceDeactivate');
  71. return false;
  72. }
  73. });
  74. }
  75. },
  76. /**
  77. * When the widget gets instantiated, the first tab that is not disabled receive focusable property
  78. * All tabs receive tabIndex 0
  79. * @private
  80. */
  81. _processTabIndex: function () {
  82. var self = this;
  83. self.triggers.attr('tabIndex', 0);
  84. $.each(this.collapsibles, function (i) {
  85. self.triggers.attr('tabIndex', 0);
  86. self.triggers.eq(i).attr('tabIndex', 0);
  87. });
  88. },
  89. /**
  90. * Prepare the elements for instantiating the collapsible widget
  91. * @private
  92. */
  93. _processPanels: function () {
  94. this.contents = this.element.find(this.options.content);
  95. this.collapsibles = this.element.find(this.options.collapsibleElement);
  96. this.collapsibles
  97. .attr('role', 'presentation')
  98. .parent()
  99. .attr('role', 'tablist');
  100. this.headers = this.element.find(this.options.header);
  101. if (this.headers.length === 0) {
  102. this.headers = this.collapsibles;
  103. }
  104. this.triggers = this.element.find(this.options.trigger);
  105. if (this.triggers.length === 0) {
  106. this.triggers = this.headers;
  107. }
  108. this._callCollapsible();
  109. },
  110. /**
  111. * Setting the disabled and active tabs and calling instantiation of collapsible
  112. * @private
  113. */
  114. _callCollapsible: function () {
  115. var self = this,
  116. disabled = false,
  117. active = false;
  118. $.each(this.collapsibles, function (i) {
  119. disabled = active = false;
  120. if ($.inArray(i, self.options.disabled) !== -1) {
  121. disabled = true;
  122. }
  123. if (i === self.options.active) {
  124. active = true;
  125. }
  126. self._instantiateCollapsible(this, i, active, disabled);
  127. });
  128. },
  129. /**
  130. * Instantiate collapsible.
  131. *
  132. * @param {HTMLElement} element
  133. * @param {Number} index
  134. * @param {*} active
  135. * @param {*} disabled
  136. * @private
  137. */
  138. _instantiateCollapsible: function (element, index, active, disabled) {
  139. $(element).collapsible(
  140. $.extend({}, this.options, {
  141. active: active,
  142. disabled: disabled,
  143. header: this.headers.eq(index),
  144. content: this.contents.eq(index),
  145. trigger: this.triggers.eq(index)
  146. })
  147. );
  148. },
  149. /**
  150. * Adding callback to close others tabs when one gets opened
  151. * @private
  152. */
  153. _closeOthers: function () {
  154. var self = this;
  155. $.each(this.collapsibles, function () {
  156. $(this).on('beforeOpen', function () {
  157. self.collapsibles.not(this).collapsible('forceDeactivate');
  158. });
  159. });
  160. },
  161. /**
  162. * @param {*} index
  163. */
  164. activate: function (index) {
  165. this._toggleActivate('activate', index);
  166. },
  167. /**
  168. * @param {*} index
  169. */
  170. deactivate: function (index) {
  171. this._toggleActivate('deactivate', index);
  172. },
  173. /**
  174. * @param {*} action
  175. * @param {*} index
  176. * @private
  177. */
  178. _toggleActivate: function (action, index) {
  179. this.collapsibles.eq(index).collapsible(action);
  180. },
  181. /**
  182. * @param {*} index
  183. */
  184. disable: function (index) {
  185. this._toggleEnable('disable', index);
  186. },
  187. /**
  188. * @param {*} index
  189. */
  190. enable: function (index) {
  191. this._toggleEnable('enable', index);
  192. },
  193. /**
  194. * @param {*} action
  195. * @param {*} index
  196. * @private
  197. */
  198. _toggleEnable: function (action, index) {
  199. var self = this;
  200. if ($.isArray(index)) {
  201. $.each(index, function () {
  202. self.collapsibles.eq(this).collapsible(action);
  203. });
  204. } else if (index === undefined) {
  205. this.collapsibles.collapsible(action);
  206. } else {
  207. this.collapsibles.eq(index).collapsible(action);
  208. }
  209. },
  210. /**
  211. * @param {jQuery.Event} event
  212. * @private
  213. */
  214. _keydown: function (event) {
  215. var self = this,
  216. keyCode, toFocus, toFocusIndex, enabledTriggers, length, currentIndex, nextToFocus;
  217. if (event.altKey || event.ctrlKey) {
  218. return;
  219. }
  220. keyCode = $.ui.keyCode;
  221. toFocus = false;
  222. enabledTriggers = [];
  223. $.each(this.triggers, function () {
  224. if (!self.collapsibles.eq(self.triggers.index($(this))).collapsible('option', 'disabled')) {
  225. enabledTriggers.push(this);
  226. }
  227. });
  228. length = $(enabledTriggers).length;
  229. currentIndex = $(enabledTriggers).index(event.target);
  230. /**
  231. * @param {String} direction
  232. * @return {*}
  233. */
  234. nextToFocus = function (direction) {
  235. if (length > 0) {
  236. if (direction === 'right') {
  237. toFocusIndex = (currentIndex + 1) % length;
  238. } else {
  239. toFocusIndex = (currentIndex + length - 1) % length;
  240. }
  241. return enabledTriggers[toFocusIndex];
  242. }
  243. return event.target;
  244. };
  245. switch (event.keyCode) {
  246. case keyCode.RIGHT:
  247. case keyCode.DOWN:
  248. toFocus = nextToFocus('right');
  249. break;
  250. case keyCode.LEFT:
  251. case keyCode.UP:
  252. toFocus = nextToFocus('left');
  253. break;
  254. case keyCode.HOME:
  255. toFocus = enabledTriggers[0];
  256. break;
  257. case keyCode.END:
  258. toFocus = enabledTriggers[length - 1];
  259. break;
  260. }
  261. if (toFocus) {
  262. toFocusIndex = this.triggers.index(toFocus);
  263. $(event.target).attr('tabIndex', -1);
  264. $(toFocus).attr('tabIndex', 0);
  265. toFocus.focus();
  266. if (this.options.openOnFocus) {
  267. this.activate(toFocusIndex);
  268. }
  269. event.preventDefault();
  270. }
  271. },
  272. /**
  273. * @private
  274. */
  275. _bind: function () {
  276. var events = {
  277. keydown: '_keydown'
  278. };
  279. this._off(this.triggers);
  280. this._on(this.triggers, events);
  281. }
  282. });
  283. return $.mage.tabs;
  284. });