annotate core/misc/vertical-tabs.es6.js @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents a9cd425dd02b
children
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * Define vertical tabs functionality.
Chris@0 4 */
Chris@0 5
Chris@0 6 /**
Chris@0 7 * Triggers when form values inside a vertical tab changes.
Chris@0 8 *
Chris@0 9 * This is used to update the summary in vertical tabs in order to know what
Chris@0 10 * are the important fields' values.
Chris@0 11 *
Chris@0 12 * @event summaryUpdated
Chris@0 13 */
Chris@0 14
Chris@4 15 (function($, Drupal, drupalSettings) {
Chris@0 16 /**
Chris@0 17 * Show the parent vertical tab pane of a targeted page fragment.
Chris@0 18 *
Chris@0 19 * In order to make sure a targeted element inside a vertical tab pane is
Chris@0 20 * visible on a hash change or fragment link click, show all parent panes.
Chris@0 21 *
Chris@0 22 * @param {jQuery.Event} e
Chris@0 23 * The event triggered.
Chris@0 24 * @param {jQuery} $target
Chris@0 25 * The targeted node as a jQuery object.
Chris@0 26 */
Chris@0 27 const handleFragmentLinkClickOrHashChange = (e, $target) => {
Chris@0 28 $target.parents('.vertical-tabs__pane').each((index, pane) => {
Chris@4 29 $(pane)
Chris@4 30 .data('verticalTab')
Chris@4 31 .focus();
Chris@0 32 });
Chris@0 33 };
Chris@0 34
Chris@0 35 /**
Chris@0 36 * This script transforms a set of details into a stack of vertical tabs.
Chris@0 37 *
Chris@0 38 * Each tab may have a summary which can be updated by another
Chris@0 39 * script. For that to work, each details element has an associated
Chris@0 40 * 'verticalTabCallback' (with jQuery.data() attached to the details),
Chris@0 41 * which is called every time the user performs an update to a form
Chris@0 42 * element inside the tab pane.
Chris@0 43 *
Chris@0 44 * @type {Drupal~behavior}
Chris@0 45 *
Chris@0 46 * @prop {Drupal~behaviorAttach} attach
Chris@0 47 * Attaches behaviors for vertical tabs.
Chris@0 48 */
Chris@0 49 Drupal.behaviors.verticalTabs = {
Chris@0 50 attach(context) {
Chris@0 51 const width = drupalSettings.widthBreakpoint || 640;
Chris@0 52 const mq = `(max-width: ${width}px)`;
Chris@0 53
Chris@0 54 if (window.matchMedia(mq).matches) {
Chris@0 55 return;
Chris@0 56 }
Chris@0 57
Chris@0 58 /**
Chris@0 59 * Binds a listener to handle fragment link clicks and URL hash changes.
Chris@0 60 */
Chris@4 61 $('body')
Chris@4 62 .once('vertical-tabs-fragments')
Chris@4 63 .on(
Chris@4 64 'formFragmentLinkClickOrHashChange.verticalTabs',
Chris@4 65 handleFragmentLinkClickOrHashChange,
Chris@4 66 );
Chris@0 67
Chris@4 68 $(context)
Chris@4 69 .find('[data-vertical-tabs-panes]')
Chris@4 70 .once('vertical-tabs')
Chris@4 71 .each(function() {
Chris@4 72 const $this = $(this).addClass('vertical-tabs__panes');
Chris@4 73 const focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
Chris@4 74 let tabFocus;
Chris@0 75
Chris@4 76 // Check if there are some details that can be converted to
Chris@4 77 // vertical-tabs.
Chris@4 78 const $details = $this.find('> details');
Chris@4 79 if ($details.length === 0) {
Chris@4 80 return;
Chris@4 81 }
Chris@0 82
Chris@4 83 // Create the tab column.
Chris@4 84 const tabList = $('<ul class="vertical-tabs__menu"></ul>');
Chris@4 85 $this
Chris@4 86 .wrap('<div class="vertical-tabs clearfix"></div>')
Chris@4 87 .before(tabList);
Chris@0 88
Chris@4 89 // Transform each details into a tab.
Chris@4 90 $details.each(function() {
Chris@4 91 const $that = $(this);
Chris@4 92 const verticalTab = new Drupal.verticalTab({
Chris@4 93 title: $that.find('> summary').text(),
Chris@4 94 details: $that,
Chris@4 95 });
Chris@4 96 tabList.append(verticalTab.item);
Chris@4 97 $that
Chris@4 98 .removeClass('collapsed')
Chris@4 99 // prop() can't be used on browsers not supporting details element,
Chris@4 100 // the style won't apply to them if prop() is used.
Chris@4 101 .attr('open', true)
Chris@4 102 .addClass('vertical-tabs__pane')
Chris@4 103 .data('verticalTab', verticalTab);
Chris@4 104 if (this.id === focusID) {
Chris@4 105 tabFocus = $that;
Chris@4 106 }
Chris@0 107 });
Chris@4 108
Chris@4 109 $(tabList)
Chris@4 110 .find('> li')
Chris@4 111 .eq(0)
Chris@4 112 .addClass('first');
Chris@4 113 $(tabList)
Chris@4 114 .find('> li')
Chris@4 115 .eq(-1)
Chris@4 116 .addClass('last');
Chris@4 117
Chris@4 118 if (!tabFocus) {
Chris@4 119 // If the current URL has a fragment and one of the tabs contains an
Chris@4 120 // element that matches the URL fragment, activate that tab.
Chris@4 121 const $locationHash = $this.find(window.location.hash);
Chris@4 122 if (window.location.hash && $locationHash.length) {
Chris@4 123 tabFocus = $locationHash.closest('.vertical-tabs__pane');
Chris@4 124 } else {
Chris@4 125 tabFocus = $this.find('> .vertical-tabs__pane').eq(0);
Chris@4 126 }
Chris@4 127 }
Chris@4 128 if (tabFocus.length) {
Chris@4 129 tabFocus.data('verticalTab').focus();
Chris@0 130 }
Chris@0 131 });
Chris@0 132 },
Chris@0 133 };
Chris@0 134
Chris@0 135 /**
Chris@0 136 * The vertical tab object represents a single tab within a tab group.
Chris@0 137 *
Chris@0 138 * @constructor
Chris@0 139 *
Chris@0 140 * @param {object} settings
Chris@0 141 * Settings object.
Chris@0 142 * @param {string} settings.title
Chris@0 143 * The name of the tab.
Chris@0 144 * @param {jQuery} settings.details
Chris@0 145 * The jQuery object of the details element that is the tab pane.
Chris@0 146 *
Chris@0 147 * @fires event:summaryUpdated
Chris@0 148 *
Chris@0 149 * @listens event:summaryUpdated
Chris@0 150 */
Chris@4 151 Drupal.verticalTab = function(settings) {
Chris@0 152 const self = this;
Chris@0 153 $.extend(this, settings, Drupal.theme('verticalTab', settings));
Chris@0 154
Chris@0 155 this.link.attr('href', `#${settings.details.attr('id')}`);
Chris@0 156
Chris@4 157 this.link.on('click', e => {
Chris@0 158 e.preventDefault();
Chris@0 159 self.focus();
Chris@0 160 });
Chris@0 161
Chris@0 162 // Keyboard events added:
Chris@0 163 // Pressing the Enter key will open the tab pane.
Chris@4 164 this.link.on('keydown', event => {
Chris@0 165 if (event.keyCode === 13) {
Chris@0 166 event.preventDefault();
Chris@0 167 self.focus();
Chris@0 168 // Set focus on the first input field of the visible details/tab pane.
Chris@4 169 $('.vertical-tabs__pane :input:visible:enabled')
Chris@4 170 .eq(0)
Chris@4 171 .trigger('focus');
Chris@0 172 }
Chris@0 173 });
Chris@0 174
Chris@0 175 this.details
Chris@0 176 .on('summaryUpdated', () => {
Chris@0 177 self.updateSummary();
Chris@0 178 })
Chris@0 179 .trigger('summaryUpdated');
Chris@0 180 };
Chris@0 181
Chris@0 182 Drupal.verticalTab.prototype = {
Chris@0 183 /**
Chris@0 184 * Displays the tab's content pane.
Chris@0 185 */
Chris@0 186 focus() {
Chris@0 187 this.details
Chris@0 188 .siblings('.vertical-tabs__pane')
Chris@4 189 .each(function() {
Chris@0 190 const tab = $(this).data('verticalTab');
Chris@0 191 tab.details.hide();
Chris@0 192 tab.item.removeClass('is-selected');
Chris@0 193 })
Chris@0 194 .end()
Chris@0 195 .show()
Chris@0 196 .siblings(':hidden.vertical-tabs__active-tab')
Chris@0 197 .val(this.details.attr('id'));
Chris@0 198 this.item.addClass('is-selected');
Chris@0 199 // Mark the active tab for screen readers.
Chris@0 200 $('#active-vertical-tab').remove();
Chris@4 201 this.link.append(
Chris@4 202 `<span id="active-vertical-tab" class="visually-hidden">${Drupal.t(
Chris@4 203 '(active tab)',
Chris@4 204 )}</span>`,
Chris@4 205 );
Chris@0 206 },
Chris@0 207
Chris@0 208 /**
Chris@0 209 * Updates the tab's summary.
Chris@0 210 */
Chris@0 211 updateSummary() {
Chris@0 212 this.summary.html(this.details.drupalGetSummary());
Chris@0 213 },
Chris@0 214
Chris@0 215 /**
Chris@0 216 * Shows a vertical tab pane.
Chris@0 217 *
Chris@0 218 * @return {Drupal.verticalTab}
Chris@0 219 * The verticalTab instance.
Chris@0 220 */
Chris@0 221 tabShow() {
Chris@0 222 // Display the tab.
Chris@0 223 this.item.show();
Chris@0 224 // Show the vertical tabs.
Chris@0 225 this.item.closest('.js-form-type-vertical-tabs').show();
Chris@0 226 // Update .first marker for items. We need recurse from parent to retain
Chris@0 227 // the actual DOM element order as jQuery implements sortOrder, but not
Chris@0 228 // as public method.
Chris@0 229 this.item
Chris@0 230 .parent()
Chris@0 231 .children('.vertical-tabs__menu-item')
Chris@0 232 .removeClass('first')
Chris@0 233 .filter(':visible')
Chris@0 234 .eq(0)
Chris@0 235 .addClass('first');
Chris@0 236 // Display the details element.
Chris@0 237 this.details.removeClass('vertical-tab--hidden').show();
Chris@0 238 // Focus this tab.
Chris@0 239 this.focus();
Chris@0 240 return this;
Chris@0 241 },
Chris@0 242
Chris@0 243 /**
Chris@0 244 * Hides a vertical tab pane.
Chris@0 245 *
Chris@0 246 * @return {Drupal.verticalTab}
Chris@0 247 * The verticalTab instance.
Chris@0 248 */
Chris@0 249 tabHide() {
Chris@0 250 // Hide this tab.
Chris@0 251 this.item.hide();
Chris@0 252 // Update .first marker for items. We need recurse from parent to retain
Chris@0 253 // the actual DOM element order as jQuery implements sortOrder, but not
Chris@0 254 // as public method.
Chris@0 255 this.item
Chris@0 256 .parent()
Chris@0 257 .children('.vertical-tabs__menu-item')
Chris@0 258 .removeClass('first')
Chris@0 259 .filter(':visible')
Chris@0 260 .eq(0)
Chris@0 261 .addClass('first');
Chris@0 262 // Hide the details element.
Chris@0 263 this.details.addClass('vertical-tab--hidden').hide();
Chris@0 264 // Focus the first visible tab (if there is one).
Chris@4 265 const $firstTab = this.details
Chris@4 266 .siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)')
Chris@4 267 .eq(0);
Chris@0 268 if ($firstTab.length) {
Chris@0 269 $firstTab.data('verticalTab').focus();
Chris@0 270 }
Chris@0 271 // Hide the vertical tabs (if no tabs remain).
Chris@0 272 else {
Chris@0 273 this.item.closest('.js-form-type-vertical-tabs').hide();
Chris@0 274 }
Chris@0 275 return this;
Chris@0 276 },
Chris@0 277 };
Chris@0 278
Chris@0 279 /**
Chris@0 280 * Theme function for a vertical tab.
Chris@0 281 *
Chris@0 282 * @param {object} settings
Chris@0 283 * An object with the following keys:
Chris@0 284 * @param {string} settings.title
Chris@0 285 * The name of the tab.
Chris@0 286 *
Chris@0 287 * @return {object}
Chris@0 288 * This function has to return an object with at least these keys:
Chris@0 289 * - item: The root tab jQuery element
Chris@0 290 * - link: The anchor tag that acts as the clickable area of the tab
Chris@0 291 * (jQuery version)
Chris@0 292 * - summary: The jQuery element that contains the tab summary
Chris@0 293 */
Chris@4 294 Drupal.theme.verticalTab = function(settings) {
Chris@0 295 const tab = {};
Chris@4 296 tab.item = $(
Chris@4 297 '<li class="vertical-tabs__menu-item" tabindex="-1"></li>',
Chris@4 298 ).append(
Chris@4 299 (tab.link = $('<a href="#"></a>')
Chris@4 300 .append(
Chris@4 301 (tab.title = $(
Chris@4 302 '<strong class="vertical-tabs__menu-item-title"></strong>',
Chris@4 303 ).text(settings.title)),
Chris@4 304 )
Chris@4 305 .append(
Chris@4 306 (tab.summary = $(
Chris@4 307 '<span class="vertical-tabs__menu-item-summary"></span>',
Chris@4 308 )),
Chris@4 309 )),
Chris@4 310 );
Chris@0 311 return tab;
Chris@0 312 };
Chris@4 313 })(jQuery, Drupal, drupalSettings);