tabs.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /* global FORM_KEY */
  6. (function (factory) {
  7. 'use strict';
  8. if (typeof define === 'function' && define.amd) {
  9. define([
  10. 'jquery',
  11. 'jquery/ui'
  12. ], factory);
  13. } else {
  14. factory(jQuery);
  15. }
  16. }(function ($) {
  17. 'use strict';
  18. var rhash, isLocal;
  19. // mage.tabs base functionality
  20. $.widget('mage.tabs', $.ui.tabs, {
  21. options: {
  22. spinner: false,
  23. groups: null,
  24. tabPanelClass: '',
  25. excludedPanel: ''
  26. },
  27. /**
  28. * Tabs creation
  29. * @protected
  30. */
  31. _create: function () {
  32. var activeIndex = this._getTabIndex(this.options.active);
  33. this.options.active = activeIndex >= 0 ? activeIndex : 0;
  34. this._super();
  35. },
  36. /**
  37. * @override
  38. * @private
  39. * @return {Array} Array of DOM-elements
  40. */
  41. _getList: function () {
  42. if (this.options.groups) {
  43. return this.element.find(this.options.groups);
  44. }
  45. return this._super();
  46. },
  47. /**
  48. * Get active anchor
  49. * @return {Element}
  50. */
  51. activeAnchor: function () {
  52. return this.anchors.eq(this.option('active'));
  53. },
  54. /**
  55. * Get tab index by tab id
  56. * @protected
  57. * @param {String} id - id of tab
  58. * @return {Number}
  59. */
  60. _getTabIndex: function (id) {
  61. var anchors = this.anchors ?
  62. this.anchors :
  63. this._getList().find('> li > a[href]');
  64. return anchors.index($('#' + id));
  65. },
  66. /**
  67. * Switch between tabs
  68. * @protected
  69. * @param {Object} event - event object
  70. * @param {undefined|Object} eventData
  71. */
  72. _toggle: function (event, eventData) {
  73. var anchor = $(eventData.newTab).find('a');
  74. if ($(eventData.newTab).find('a').data().tabType === 'link') {
  75. location.href = anchor.prop('href');
  76. } else {
  77. this._superApply(arguments);
  78. }
  79. }
  80. });
  81. rhash = /#.*$/;
  82. /**
  83. * @param {*} anchor
  84. * @return {Boolean}
  85. */
  86. isLocal = function (anchor) {
  87. return anchor.hash.length > 1 &&
  88. anchor.href.replace(rhash, '') ===
  89. location.href.replace(rhash, '')
  90. // support: Safari 5.1
  91. // Safari 5.1 doesn't encode spaces in window.location
  92. // but it does encode spaces from anchors (#8777)
  93. .replace(/\s/g, '%20');
  94. };
  95. // Extension for mage.tabs - Move panels in destination element
  96. $.widget('mage.tabs', $.mage.tabs, {
  97. /**
  98. * Move panels in destination element on creation
  99. * @protected
  100. * @override
  101. */
  102. _create: function () {
  103. this._super();
  104. this._movePanelsInDestination(this.panels);
  105. },
  106. /**
  107. * Get panel for tab. If panel no exist in tabs container, then find panel in destination element
  108. * @protected
  109. * @override
  110. * @param {Element} tab - tab "li" DOM-element
  111. * @return {Element}
  112. */
  113. _getPanelForTab: function (tab) {
  114. var panel = this._superApply(arguments),
  115. id;
  116. if (!panel.length) {
  117. id = $(tab).attr('aria-controls');
  118. panel = $(this.options.destination).find(this._sanitizeSelector('#' + id));
  119. }
  120. return panel;
  121. },
  122. /**
  123. * @private
  124. */
  125. _processTabs: function () {
  126. var that = this;
  127. this.tablist = this._getList()
  128. .addClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all')
  129. .attr('role', 'tablist');
  130. this.tabs = this.tablist.find('> li:has(a[href])')
  131. .addClass('ui-state-default ui-corner-top')
  132. .attr({
  133. role: 'tab',
  134. tabIndex: -1
  135. });
  136. this.anchors = this.tabs.map(function () {
  137. return $('a', this)[ 0 ];
  138. })
  139. .addClass('ui-tabs-anchor')
  140. .attr({
  141. role: 'presentation',
  142. tabIndex: -1
  143. });
  144. this.panels = $();
  145. this.anchors.each(function (i, anchor) {
  146. var selector, panel, panelId,
  147. anchorId = $(anchor).uniqueId().attr('id'),
  148. tab = $(anchor).closest('li'),
  149. originalAriaControls = tab.attr('aria-controls');
  150. // inline tab
  151. if (isLocal(anchor)) {
  152. selector = anchor.hash;
  153. panel = that.document.find(that._sanitizeSelector(selector));
  154. // remote tab
  155. } else {
  156. panelId = that._tabId(tab);
  157. selector = '#' + panelId;
  158. panel = that.document.find(selector);
  159. if (!panel.length) {
  160. panel = that._createPanel(panelId);
  161. panel.insertAfter(that.panels[ i - 1 ] || that.tablist);
  162. }
  163. panel.attr('aria-live', 'polite');
  164. }
  165. if (panel.length) {
  166. that.panels = that.panels.add(panel);
  167. }
  168. if (originalAriaControls) {
  169. tab.data('ui-tabs-aria-controls', originalAriaControls);
  170. }
  171. tab.attr({
  172. 'aria-controls': selector.substring(1),
  173. 'aria-labelledby': anchorId
  174. });
  175. panel.attr('aria-labelledby', anchorId);
  176. if (that.options.excludedPanel.indexOf(anchorId + '_content') < 0) {
  177. panel.addClass(that.options.tabPanelClass);
  178. }
  179. });
  180. this.panels
  181. .addClass('ui-tabs-panel ui-widget-content ui-corner-bottom')
  182. .attr('role', 'tabpanel');
  183. },
  184. /**
  185. * Move panels in destination element
  186. * @protected
  187. * @override
  188. */
  189. _movePanelsInDestination: function (panels) {
  190. if (this.options.destination && !panels.parents(this.options.destination).length) {
  191. this.element.trigger('beforePanelsMove', panels);
  192. panels.find('script:not([type]), script[type="text/javascript"]').remove();
  193. panels.appendTo(this.options.destination)
  194. .each($.proxy(function (i, panel) {
  195. $(panel).trigger('move.tabs', this.anchors.eq(i));
  196. }, this));
  197. }
  198. },
  199. /**
  200. * Move panels in destination element on tabs switching
  201. * @protected
  202. * @override
  203. * @param {Object} event - event object
  204. * @param {Object} eventData
  205. */
  206. _toggle: function (event, eventData) {
  207. this._movePanelsInDestination(eventData.newPanel);
  208. this._superApply(arguments);
  209. }
  210. });
  211. // Extension for mage.tabs - Ajax functionality for tabs
  212. $.widget('mage.tabs', $.mage.tabs, {
  213. options: {
  214. ajaxOptions: {
  215. data: {
  216. isAjax: true,
  217. 'form_key': typeof FORM_KEY !== 'undefined' ? FORM_KEY : null
  218. }
  219. },
  220. /**
  221. * Replacing href attribute with loaded panel id
  222. * @param {Object} event - event object
  223. * @param {Object} ui
  224. */
  225. load: function (event, ui) {
  226. var panel = $(ui.panel);
  227. $(ui.tab).prop('href', '#' + panel.prop('id'));
  228. panel.trigger('contentUpdated');
  229. }
  230. }
  231. });
  232. // Extension for mage.tabs - Attach event handlers to tabs
  233. $.widget('mage.tabs', $.mage.tabs, {
  234. options: {
  235. tabIdArgument: 'tab',
  236. tabsBlockPrefix: null
  237. },
  238. /**
  239. * Attach event handlers to tabs, on creation
  240. * @protected
  241. * @override
  242. */
  243. _refresh: function () {
  244. this._super();
  245. $.each(this.tabs, $.proxy(function (i, tab) {
  246. $(this._getPanelForTab(tab))
  247. .off('changed' + this.eventNamespace)
  248. .off('highlight.validate' + this.eventNamespace)
  249. .off('focusin' + this.eventNamespace)
  250. .on('changed' + this.eventNamespace, {
  251. index: i
  252. }, $.proxy(this._onContentChange, this))
  253. .on('highlight.validate' + this.eventNamespace, {
  254. index: i
  255. }, $.proxy(this._onInvalid, this))
  256. .on('focusin' + this.eventNamespace, {
  257. index: i
  258. }, $.proxy(this._onFocus, this));
  259. }, this));
  260. ($(this.options.destination).is('form') ?
  261. $(this.options.destination) :
  262. $(this.options.destination).closest('form'))
  263. .off('beforeSubmit' + this.eventNamespace)
  264. .on('beforeSubmit' + this.eventNamespace, $.proxy(this._onBeforeSubmit, this));
  265. },
  266. /**
  267. * Mark tab as changed if some field inside tab panel is changed
  268. * @protected
  269. * @param {Object} e - event object
  270. */
  271. _onContentChange: function (e) {
  272. var cssChanged = '_changed';
  273. this.anchors.eq(e.data.index).addClass(cssChanged);
  274. this._updateNavTitleMessages(e, cssChanged);
  275. },
  276. /**
  277. * Clone messages (tooltips) from anchor to parent element
  278. * @protected
  279. * @param {Object} e - event object
  280. * @param {String} messageType - changed or error
  281. */
  282. _updateNavTitleMessages: function (e, messageType) {
  283. var curAnchor = this.anchors.eq(e.data.index),
  284. curItem = curAnchor.parents('[data-role="container"]').find('[data-role="title"]'),
  285. curItemMessages = curItem.find('[data-role="title-messages"]'),
  286. activeClass = '_active';
  287. if (curItemMessages.is(':empty')) {
  288. curAnchor
  289. .find('[data-role="item-messages"]')
  290. .clone()
  291. .appendTo(curItemMessages);
  292. }
  293. curItemMessages.find('.' + messageType).addClass(activeClass);
  294. },
  295. /**
  296. * Mark tab as error if some field inside tab panel is not passed validation
  297. * @param {Object} e - event object
  298. * @protected
  299. */
  300. _onInvalid: function (e) {
  301. var cssError = '_error',
  302. fakeEvent = e;
  303. fakeEvent.currentTarget = $(this.anchors).eq(e.data.index);
  304. this._eventHandler(fakeEvent);
  305. this.anchors.eq(e.data.index).addClass(cssError).find('.' + cssError).show();
  306. this._updateNavTitleMessages(e, cssError);
  307. },
  308. /**
  309. * Show tab panel if focus event triggered of some field inside tab panel
  310. * @param {Object} e - event object
  311. * @protected
  312. */
  313. _onFocus: function (e) {
  314. this.option('_active', e.data.index);
  315. },
  316. /**
  317. * Add active tab id in data object when "beforeSubmit" event is triggered
  318. * @param {Object} e - event object
  319. * @param {Object} data - event data object
  320. * @protected
  321. */
  322. _onBeforeSubmit: function (e, data) { //eslint-disable-line no-unused-vars
  323. var activeAnchor = this.activeAnchor(),
  324. activeTabId = activeAnchor.prop('id'),
  325. options;
  326. if (this.options.tabsBlockPrefix) {
  327. if (activeAnchor.is('[id*="' + this.options.tabsBlockPrefix + '"]')) {
  328. activeTabId = activeAnchor.prop('id').substr(this.options.tabsBlockPrefix.length);
  329. }
  330. }
  331. $(this.anchors).removeClass('error');
  332. options = {
  333. action: {
  334. args: {}
  335. }
  336. };
  337. options.action.args[this.options.tabIdArgument] = activeTabId;
  338. data = data ? $.extend(data, options) : options;
  339. }
  340. });
  341. // Extension for mage.tabs - Shadow tabs functionality
  342. $.widget('mage.tabs', $.mage.tabs, {
  343. /**
  344. * Add shadow tabs functionality on creation
  345. * @protected
  346. * @override
  347. */
  348. _refresh: function () {
  349. var anchors, shadowTabs, tabs;
  350. this._super();
  351. anchors = this.anchors;
  352. shadowTabs = this.options.shadowTabs;
  353. tabs = this.tabs;
  354. if (shadowTabs) {
  355. anchors.each($.proxy(function (i, anchor) {
  356. var anchorId = $(anchor).prop('id');
  357. if (shadowTabs[anchorId]) {
  358. $(anchor).parents('li').on('click', $.proxy(function () {
  359. $.each(shadowTabs[anchorId], $.proxy(function (key, id) {
  360. this.load($(tabs).index($('#' + id).parents('li')), {});
  361. }, this));
  362. }, this));
  363. }
  364. }, this));
  365. }
  366. }
  367. });
  368. return $.mage.tabs;
  369. }));