express-8.x-1.x-dev/themes/contrib/bootstrap/js/misc/vertical-tabs.js
themes/contrib/bootstrap/js/misc/vertical-tabs.js
/** * @file * Overrides core/misc/vertical-tabs.js. */ (function ($, window, Drupal, drupalSettings) { "use strict"; /** * This script transforms a set of details into a stack of vertical * tabs. Another tab pane can be selected by clicking on the respective * tab. * * Each tab may have a summary which can be updated by another * script. For that to work, each details element has an associated * 'verticalTabCallback' (with jQuery.data() attached to the details), * which is called every time the user performs an update to a form * element inside the tab pane. */ Drupal.behaviors.verticalTabs = { attach: function (context) { var width = drupalSettings.widthBreakpoint || 640; var mq = '(max-width: ' + width + 'px)'; if (window.matchMedia(mq).matches) { return; } $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () { var $this = $(this).addClass('tab-content vertical-tabs-panes'); var focusID = $(':hidden.vertical-tabs__active-tab', this).val(); if (typeof focusID === 'undefined' || !focusID.length) { focusID = false; } var tab_focus; // Check if there are some details that can be converted to vertical-tabs var $details = $this.find('> .panel'); if ($details.length === 0) { return; } // Create the tab column. var tab_list = $('<ul class="nav nav-tabs vertical-tabs-list"></ul>'); $this.wrap('<div class="tabbable tabs-left vertical-tabs clearfix"></div>').before(tab_list); // Transform each details into a tab. $details.each(function () { var $that = $(this); var vertical_tab = new Drupal.verticalTab({ title: $that.find('> .panel-heading > .panel-title, > .panel-heading').last().html(), details: $that }); tab_list.append(vertical_tab.item); $that .removeClass('collapsed') // prop() can't be used on browsers not supporting details element, // the style won't apply to them if prop() is used. .attr('open', true) .removeClass('collapsible collapsed panel panel-default') .addClass('tab-pane vertical-tabs-pane') .data('verticalTab', vertical_tab) .find('> .panel-heading').remove(); if (this.id === focusID) { tab_focus = $that; } }); $(tab_list).find('> li:first').addClass('first'); $(tab_list).find('> li:last').addClass('last'); if (!tab_focus) { // If the current URL has a fragment and one of the tabs contains an // element that matches the URL fragment, activate that tab. var $locationHash = $this.find(window.location.hash); if (window.location.hash && $locationHash.length) { tab_focus = $locationHash.closest('.vertical-tabs-pane'); } else { tab_focus = $this.find('> .vertical-tabs-pane:first'); } } if (tab_focus.length) { tab_focus.data('verticalTab').focus(); } }); // Provide some Bootstrap tab/Drupal integration. // @todo merge this into the above code from core. $(context).find('.tabbable').once('bootstrap-tabs').each(function () { var $wrapper = $(this); var $tabs = $wrapper.find('.nav-tabs'); var $content = $wrapper.find('.tab-content'); var borderRadius = parseInt($content.css('borderBottomRightRadius'), 10); var bootstrapTabResize = function() { if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) { $content.css('min-height', $tabs.outerHeight()); } }; // Add min-height on content for left and right tabs. bootstrapTabResize(); // Detect tab switch. if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) { $tabs.on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) { bootstrapTabResize(); if ($wrapper.hasClass('tabs-left')) { if ($(e.target).parent().is(':first-child')) { $content.css('borderTopLeftRadius', '0'); } else { $content.css('borderTopLeftRadius', borderRadius + 'px'); } } else { if ($(e.target).parent().is(':first-child')) { $content.css('borderTopRightRadius', '0'); } else { $content.css('borderTopRightRadius', borderRadius + 'px'); } } }); } }); } }; /** * The vertical tab object represents a single tab within a tab group. * * @param settings * An object with the following keys: * - title: The name of the tab. * - details: The jQuery object of the details element that is the tab pane. */ Drupal.verticalTab = function (settings) { var self = this; $.extend(this, settings, Drupal.theme('verticalTab', settings)); this.link.attr('href', '#' + settings.details.attr('id')); this.link.on('click', function (e) { e.preventDefault(); self.focus(); }); // Keyboard events added: // Pressing the Enter key will open the tab pane. this.link.on('keydown', function (event) { event.preventDefault(); if (event.keyCode === 13) { self.focus(); // Set focus on the first input field of the visible details/tab pane. $(".vertical-tabs-pane :input:visible:enabled:first").trigger('focus'); } }); this.details .on('summaryUpdated', function () { self.updateSummary(); }) .trigger('summaryUpdated'); }; Drupal.verticalTab.prototype = { /** * Displays the tab's content pane. */ focus: function () { this.details .siblings('.vertical-tabs-pane') .each(function () { $(this).removeClass('active').find('> div').removeClass('in'); var tab = $(this).data('verticalTab'); tab.item.removeClass('selected'); }) .end() .addClass('active') .siblings(':hidden.vertical-tabs-active-tab') .val(this.details.attr('id')); this.details.find('> div').addClass('in'); this.details.data('verticalTab').item.find('a').tab('show'); this.item.addClass('selected'); // Mark the active tab for screen readers. $('#active-vertical-tab').remove(); this.link.append('<span id="active-vertical-tab" class="visually-hidden">' + Drupal.t('(active tab)') + '</span>'); }, /** * Updates the tab's summary. */ updateSummary: function () { this.summary.html(this.details.drupalGetSummary()); }, /** * Shows a vertical tab pane. */ tabShow: function () { // Display the tab. this.item.show(); // Show the vertical tabs. this.item.closest('.form-type-vertical-tabs').show(); // Update .first marker for items. We need recurse from parent to retain the // actual DOM element order as jQuery implements sortOrder, but not as public // method. this.item.parent().children('.vertical-tab-button').removeClass('first') .filter(':visible:first').addClass('first'); // Display the details element. this.details.removeClass('vertical-tab-hidden').show(); // Focus this tab. this.focus(); return this; }, /** * Hides a vertical tab pane. */ tabHide: function () { // Hide this tab. this.item.hide(); // Update .first marker for items. We need recurse from parent to retain the // actual DOM element order as jQuery implements sortOrder, but not as public // method. this.item.parent().children('.vertical-tab-button').removeClass('first') .filter(':visible:first').addClass('first'); // Hide the details element. this.details.addClass('vertical-tab-hidden').hide(); // Focus the first visible tab (if there is one). var $firstTab = this.details.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first'); if ($firstTab.length) { $firstTab.data('verticalTab').focus(); } // Hide the vertical tabs (if no tabs remain). else { this.item.closest('.form-type-vertical-tabs').hide(); } return this; } }; /** * Theme function for a vertical tab. * * @param settings * An object with the following keys: * - title: The name of the tab. * @return * This function has to return an object with at least these keys: * - item: The root tab jQuery element * - link: The anchor tag that acts as the clickable area of the tab * (jQuery version) * - summary: The jQuery element that contains the tab summary */ Drupal.theme.verticalTab = function (settings) { var tab = {}; tab.item = $('<li class="vertical-tab-button" tabindex="-1"></li>') .append(tab.link = $('<a href="#' + settings.details[0].id + '" data-toggle="tab"></a>') .append(tab.title = $('<span></span>').html(settings.title)) .append(tab.summary = $('<div class="summary"></div>') ) ); return tab; }; })(jQuery, this, Drupal, drupalSettings);