1/** 2 * @file 3 * Overrides core/misc/vertical-tabs.js. 4 */ 5 6(function ($, window, Drupal, drupalSettings, once) { 7 "use strict"; 8 9 /** 10 * Show the parent vertical tab pane of a targeted page fragment. 11 * 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. 14 * 15 * @param {jQuery.Event} e 16 * The event triggered. 17 * @param {jQuery} $target 18 * The targeted node as a jQuery object. 19 */ 20 var handleFragmentLinkClickOrHashChange = function handleFragmentLinkClickOrHashChange(e, $target) { 21 $target.parents('.vertical-tabs-pane').each(function (index, pane) { 22 $(pane).data('verticalTab').focus(); 23 }); 24 }; 25 26 /** 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 29 * tab. 30 * 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. 36 */ 37 Drupal.behaviors.verticalTabs = { 38 attach: function (context) { 39 var width = drupalSettings.widthBreakpoint || 640; 40 var mq = '(max-width: ' + width + 'px)'; 41 42 if (window.matchMedia(mq).matches) { 43 return; 44 } 45 46 /** 47 * Binds a listener to handle fragment link clicks and URL hash changes. 48 */ 49 $(once('vertical-tabs-fragments', 'body')).on('formFragmentLinkClickOrHashChange.verticalTabs', handleFragmentLinkClickOrHashChange); 50 51 $(once('vertical-tabs', '[data-vertical-tabs-panes]', context)).each(function () { 52 var $this = $(this).addClass('tab-content vertical-tabs-panes'); 53 54 var focusID = $(':hidden.vertical-tabs__active-tab', this).val(); 55 if (typeof focusID === 'undefined' || !focusID.length) { 56 focusID = false; 57 } 58 var tab_focus; 59 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) { 63 return; 64 } 65 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); 69 70 // Transform each details into a tab. 71 $details.each(function () { 72 var $that = $(this); 73 var vertical_tab = new Drupal.verticalTab({ 74 title: $that.find('> .panel-heading > .panel-title, > .panel-heading').last().html(), 75 details: $that 76 }); 77 tab_list.append(vertical_tab.item); 78 $that 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. 82 .attr('open', true) 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) { 88 tab_focus = $that; 89 } 90 }); 91 92 $(tab_list).find('> li:first').addClass('first'); 93 $(tab_list).find('> li:last').addClass('last'); 94 95 if (!tab_focus) { 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'); 101 } 102 else { 103 tab_focus = $this.find('> .vertical-tabs-pane:first'); 104 } 105 } 106 if (tab_focus.length) { 107 tab_focus.data('verticalTab').focus(); 108 } 109 }); 110 111 // Provide some Bootstrap tab/Drupal integration. 112 // @todo merge this into the above code from core. 113 $(once('bootstrap-tabs', '.tabbable', context)).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()); 121 } 122 }; 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'); 132 } 133 else { 134 $content.css('borderTopLeftRadius', borderRadius + 'px'); 135 } 136 } 137 else { 138 if ($(e.target).parent().is(':first-child')) { 139 $content.css('borderTopRightRadius', '0'); 140 } 141 else { 142 $content.css('borderTopRightRadius', borderRadius + 'px'); 143 } 144 } 145 }); 146 } 147 }); 148 } 149 }; 150 151 /** 152 * The vertical tab object represents a single tab within a tab group. 153 * 154 * @param settings 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. 158 */ 159 Drupal.verticalTab = function (settings) { 160 var self = this; 161 $.extend(this, settings, Drupal.theme('verticalTab', settings)); 162 163 this.link.attr('href', '#' + settings.details.attr('id')); 164 165 this.link.on('click', function (e) { 166 e.preventDefault(); 167 self.focus(); 168 }); 169 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(); 175 self.focus(); 176 // Set focus on the first input field of the visible details/tab pane. 177 $(".vertical-tabs-pane :input:visible:enabled:first").trigger('focus'); 178 } 179 }); 180 181 this.details 182 .on('summaryUpdated', function () { 183 self.updateSummary(); 184 }) 185 .trigger('summaryUpdated'); 186 }; 187 188 Drupal.verticalTab.prototype = { 189 /**
190 * Displays the tab's content pane. 191 */ 192 focus: function () { 193 this.details 194 .siblings('.vertical-tabs-pane') 195 .each(function () { 196 $(this).removeClass('active').find('> div').removeClass('in'); 197 var tab = $(this).data('verticalTab'); 198 tab.item.removeClass('selected'); 199 }) 200 .end() 201 .addClass('active') 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>'); 210 }, 211 212 /** 213 * Updates the tab's summary. 214 */ 215 updateSummary: function () { 216 this.summary.html(this.details.drupalGetSummary()); 217 }, 218 219 /** 220 * Shows a vertical tab pane. 221 */ 222 tabShow: function () { 223 // Display the tab. 224 this.item.show(); 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 229 // method. 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(); 234 // Focus this tab. 235 this.focus(); 236 return this; 237 }, 238 239 /** 240 * Hides a vertical tab pane. 241 */ 242 tabHide: function () { 243 // Hide this tab. 244 this.item.hide(); 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 247 // method. 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(); 256 } 257 // Hide the vertical tabs (if no tabs remain). 258 else { 259 this.item.closest('.form-type-vertical-tabs').hide(); 260 } 261 return this; 262 } 263 }; 264 265 /** 266 * Theme function for a vertical tab. 267 * 268 * @param settings 269 * An object with the following keys: 270 * - title: The name of the tab. 271 * @return 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 275 * (jQuery version) 276 * - summary: The jQuery element that contains the tab summary 277 */ 278 Drupal.theme.verticalTab = function (settings) { 279 var tab = {}; 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>') 284 ) 285 ); 286 return tab; 287 }; 288 289})(jQuery, this, Drupal, drupalSettings, once);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.