3 * Define vertical tabs functionality.
7 * Triggers when form values inside a vertical tab changes.
9 * This is used to update the summary in vertical tabs in order to know what
10 * are the important fields' values.
12 * @event summaryUpdated
15 (function ($, Drupal, drupalSettings) {
20 * This script transforms a set of details into a stack of vertical tabs.
22 * Each tab may have a summary which can be updated by another
23 * script. For that to work, each details element has an associated
24 * 'verticalTabCallback' (with jQuery.data() attached to the details),
25 * which is called every time the user performs an update to a form
26 * element inside the tab pane.
28 * @type {Drupal~behavior}
30 * @prop {Drupal~behaviorAttach} attach
31 * Attaches behaviors for vertical tabs.
33 Drupal.behaviors.verticalTabs = {
34 attach: function (context) {
35 var width = drupalSettings.widthBreakpoint || 640;
36 var mq = '(max-width: ' + width + 'px)';
38 if (window.matchMedia(mq).matches) {
42 $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () {
43 var $this = $(this).addClass('vertical-tabs__panes');
44 var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
47 // Check if there are some details that can be converted to
49 var $details = $this.find('> details');
50 if ($details.length === 0) {
54 // Create the tab column.
55 var tab_list = $('<ul class="vertical-tabs__menu"></ul>');
56 $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
58 // Transform each details into a tab.
59 $details.each(function () {
61 var vertical_tab = new Drupal.verticalTab({
62 title: $that.find('> summary').text(),
65 tab_list.append(vertical_tab.item);
67 .removeClass('collapsed')
68 // prop() can't be used on browsers not supporting details element,
69 // the style won't apply to them if prop() is used.
71 .addClass('vertical-tabs__pane')
72 .data('verticalTab', vertical_tab);
73 if (this.id === focusID) {
78 $(tab_list).find('> li').eq(0).addClass('first');
79 $(tab_list).find('> li').eq(-1).addClass('last');
82 // If the current URL has a fragment and one of the tabs contains an
83 // element that matches the URL fragment, activate that tab.
84 var $locationHash = $this.find(window.location.hash);
85 if (window.location.hash && $locationHash.length) {
86 tab_focus = $locationHash.closest('.vertical-tabs__pane');
89 tab_focus = $this.find('> .vertical-tabs__pane').eq(0);
92 if (tab_focus.length) {
93 tab_focus.data('verticalTab').focus();
100 * The vertical tab object represents a single tab within a tab group.
104 * @param {object} settings
106 * @param {string} settings.title
107 * The name of the tab.
108 * @param {jQuery} settings.details
109 * The jQuery object of the details element that is the tab pane.
111 * @fires event:summaryUpdated
113 * @listens event:summaryUpdated
115 Drupal.verticalTab = function (settings) {
117 $.extend(this, settings, Drupal.theme('verticalTab', settings));
119 this.link.attr('href', '#' + settings.details.attr('id'));
121 this.link.on('click', function (e) {
126 // Keyboard events added:
127 // Pressing the Enter key will open the tab pane.
128 this.link.on('keydown', function (event) {
129 if (event.keyCode === 13) {
130 event.preventDefault();
132 // Set focus on the first input field of the visible details/tab pane.
133 $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus');
138 .on('summaryUpdated', function () {
139 self.updateSummary();
141 .trigger('summaryUpdated');
144 Drupal.verticalTab.prototype = {
147 * Displays the tab's content pane.
151 .siblings('.vertical-tabs__pane')
153 var tab = $(this).data('verticalTab');
155 tab.item.removeClass('is-selected');
159 .siblings(':hidden.vertical-tabs__active-tab')
160 .val(this.details.attr('id'));
161 this.item.addClass('is-selected');
162 // Mark the active tab for screen readers.
163 $('#active-vertical-tab').remove();
164 this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
168 * Updates the tab's summary.
170 updateSummary: function () {
171 this.summary.html(this.details.drupalGetSummary());
175 * Shows a vertical tab pane.
177 * @return {Drupal.verticalTab}
178 * The verticalTab instance.
180 tabShow: function () {
183 // Show the vertical tabs.
184 this.item.closest('.js-form-type-vertical-tabs').show();
185 // Update .first marker for items. We need recurse from parent to retain
186 // the actual DOM element order as jQuery implements sortOrder, but not
188 this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
189 .filter(':visible').eq(0).addClass('first');
190 // Display the details element.
191 this.details.removeClass('vertical-tab--hidden').show();
198 * Hides a vertical tab pane.
200 * @return {Drupal.verticalTab}
201 * The verticalTab instance.
203 tabHide: function () {
206 // Update .first marker for items. We need recurse from parent to retain
207 // the actual DOM element order as jQuery implements sortOrder, but not
209 this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')
210 .filter(':visible').eq(0).addClass('first');
211 // Hide the details element.
212 this.details.addClass('vertical-tab--hidden').hide();
213 // Focus the first visible tab (if there is one).
214 var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0);
215 if ($firstTab.length) {
216 $firstTab.data('verticalTab').focus();
218 // Hide the vertical tabs (if no tabs remain).
220 this.item.closest('.js-form-type-vertical-tabs').hide();
227 * Theme function for a vertical tab.
229 * @param {object} settings
230 * An object with the following keys:
231 * @param {string} settings.title
232 * The name of the tab.
235 * This function has to return an object with at least these keys:
236 * - item: The root tab jQuery element
237 * - link: The anchor tag that acts as the clickable area of the tab
239 * - summary: The jQuery element that contains the tab summary
241 Drupal.theme.verticalTab = function (settings) {
243 tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>')
244 .append(tab.link = $('<a href="#"></a>')
245 .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title))
246 .append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>')
252 })(jQuery, Drupal, drupalSettings);