3 * Overrides core/misc/vertical-tabs.js.
6 (function ($, window, Drupal, drupalSettings) {
10 * Show the parent vertical tab pane of a targeted page fragment.
12 * In order to make sure a targeted element inside a vertical tab pane is
13 * visible on a hash change or fragment link click, show all parent panes.
15 * @param {jQuery.Event} e
16 * The event triggered.
17 * @param {jQuery} $target
18 * The targeted node as a jQuery object.
20 var handleFragmentLinkClickOrHashChange = function handleFragmentLinkClickOrHashChange(e, $target) {
21 $target.parents('.vertical-tabs-pane').each(function (index, pane) {
22 $(pane).data('verticalTab').focus();
27 * This script transforms a set of details into a stack of vertical
28 * tabs. Another tab pane can be selected by clicking on the respective
31 * Each tab may have a summary which can be updated by another
32 * script. For that to work, each details element has an associated
33 * 'verticalTabCallback' (with jQuery.data() attached to the details),
34 * which is called every time the user performs an update to a form
35 * element inside the tab pane.
37 Drupal.behaviors.verticalTabs = {
38 attach: function (context) {
39 var width = drupalSettings.widthBreakpoint || 640;
40 var mq = '(max-width: ' + width + 'px)';
42 if (window.matchMedia(mq).matches) {
47 * Binds a listener to handle fragment link clicks and URL hash changes.
49 $('body').once('vertical-tabs-fragments').on('formFragmentLinkClickOrHashChange.verticalTabs', handleFragmentLinkClickOrHashChange);
51 $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () {
52 var $this = $(this).addClass('tab-content vertical-tabs-panes');
54 var focusID = $(':hidden.vertical-tabs__active-tab', this).val();
55 if (typeof focusID === 'undefined' || !focusID.length) {
60 // Check if there are some details that can be converted to vertical-tabs
61 var $details = $this.find('> .panel');
62 if ($details.length === 0) {
66 // Create the tab column.
67 var tab_list = $('<ul class="nav nav-tabs vertical-tabs-list"></ul>');
68 $this.wrap('<div class="tabbable tabs-left vertical-tabs clearfix"></div>').before(tab_list);
70 // Transform each details into a tab.
71 $details.each(function () {
73 var vertical_tab = new Drupal.verticalTab({
74 title: $that.find('> .panel-heading > .panel-title, > .panel-heading').last().html(),
77 tab_list.append(vertical_tab.item);
79 .removeClass('collapsed')
80 // prop() can't be used on browsers not supporting details element,
81 // the style won't apply to them if prop() is used.
83 .removeClass('collapsible collapsed panel panel-default')
84 .addClass('tab-pane vertical-tabs-pane')
85 .data('verticalTab', vertical_tab)
86 .find('> .panel-heading').remove();
87 if (this.id === focusID) {
92 $(tab_list).find('> li:first').addClass('first');
93 $(tab_list).find('> li:last').addClass('last');
96 // If the current URL has a fragment and one of the tabs contains an
97 // element that matches the URL fragment, activate that tab.
98 var $locationHash = $this.find(window.location.hash);
99 if (window.location.hash && $locationHash.length) {
100 tab_focus = $locationHash.closest('.vertical-tabs-pane');
103 tab_focus = $this.find('> .vertical-tabs-pane:first');
106 if (tab_focus.length) {
107 tab_focus.data('verticalTab').focus();
111 // Provide some Bootstrap tab/Drupal integration.
112 // @todo merge this into the above code from core.
113 $(context).find('.tabbable').once('bootstrap-tabs').each(function () {
114 var $wrapper = $(this);
115 var $tabs = $wrapper.find('.nav-tabs');
116 var $content = $wrapper.find('.tab-content');
117 var borderRadius = parseInt($content.css('borderBottomRightRadius'), 10);
118 var bootstrapTabResize = function() {
119 if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) {
120 $content.css('min-height', $tabs.outerHeight());
123 // Add min-height on content for left and right tabs.
124 bootstrapTabResize();
125 // Detect tab switch.
126 if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) {
127 $tabs.on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
128 bootstrapTabResize();
129 if ($wrapper.hasClass('tabs-left')) {
130 if ($(e.target).parent().is(':first-child')) {
131 $content.css('borderTopLeftRadius', '0');
134 $content.css('borderTopLeftRadius', borderRadius + 'px');
138 if ($(e.target).parent().is(':first-child')) {
139 $content.css('borderTopRightRadius', '0');
142 $content.css('borderTopRightRadius', borderRadius + 'px');
152 * The vertical tab object represents a single tab within a tab group.
155 * An object with the following keys:
156 * - title: The name of the tab.
157 * - details: The jQuery object of the details element that is the tab pane.
159 Drupal.verticalTab = function (settings) {
161 $.extend(this, settings, Drupal.theme('verticalTab', settings));
163 this.link.attr('href', '#' + settings.details.attr('id'));
165 this.link.on('click', function (e) {
170 // Keyboard events added:
171 // Pressing the Enter key will open the tab pane.
172 this.link.on('keydown', function (event) {
173 if (event.keyCode === 13) {
174 event.preventDefault();
176 // Set focus on the first input field of the visible details/tab pane.
177 $(".vertical-tabs-pane :input:visible:enabled:first").trigger('focus');
182 .on('summaryUpdated', function () {
183 self.updateSummary();
185 .trigger('summaryUpdated');
188 Drupal.verticalTab.prototype = {
190 * Displays the tab's content pane.
194 .siblings('.vertical-tabs-pane')
196 $(this).removeClass('active').find('> div').removeClass('in');
197 var tab = $(this).data('verticalTab');
198 tab.item.removeClass('selected');
202 .siblings(':hidden.vertical-tabs-active-tab')
203 .val(this.details.attr('id'));
204 this.details.find('> div').addClass('in');
205 this.details.data('verticalTab').item.find('a').tab('show');
206 this.item.addClass('selected');
207 // Mark the active tab for screen readers.
208 $('#active-vertical-tab').remove();
209 this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>');
213 * Updates the tab's summary.
215 updateSummary: function () {
216 this.summary.html(this.details.drupalGetSummary());
220 * Shows a vertical tab pane.
222 tabShow: function () {
225 // Show the vertical tabs.
226 this.item.closest('.form-type-vertical-tabs').show();
227 // Update .first marker for items. We need recurse from parent to retain the
228 // actual DOM element order as jQuery implements sortOrder, but not as public
230 this.item.parent().children('.vertical-tab-button').removeClass('first')
231 .filter(':visible:first').addClass('first');
232 // Display the details element.
233 this.details.removeClass('vertical-tab-hidden').show();
240 * Hides a vertical tab pane.
242 tabHide: function () {
245 // Update .first marker for items. We need recurse from parent to retain the
246 // actual DOM element order as jQuery implements sortOrder, but not as public
248 this.item.parent().children('.vertical-tab-button').removeClass('first')
249 .filter(':visible:first').addClass('first');
250 // Hide the details element.
251 this.details.addClass('vertical-tab-hidden').hide();
252 // Focus the first visible tab (if there is one).
253 var $firstTab = this.details.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first');
254 if ($firstTab.length) {
255 $firstTab.data('verticalTab').focus();
257 // Hide the vertical tabs (if no tabs remain).
259 this.item.closest('.form-type-vertical-tabs').hide();
266 * Theme function for a vertical tab.
269 * An object with the following keys:
270 * - title: The name of the tab.
272 * This function has to return an object with at least these keys:
273 * - item: The root tab jQuery element
274 * - link: The anchor tag that acts as the clickable area of the tab
276 * - summary: The jQuery element that contains the tab summary
278 Drupal.theme.verticalTab = function (settings) {
280 tab.item = $('<li class="vertical-tab-button" tabindex="-1"></li>')
281 .append(tab.link = $('<a href="#' + settings.details[0].id + '" data-toggle="tab"></a>')
282 .append(tab.title = $('<span></span>').html(settings.title))
283 .append(tab.summary = $('<div class="summary"></div>')
289 })(jQuery, this, Drupal, drupalSettings);