annotate core/misc/vertical-tabs.es6.js @ 0:c75dbcec494b

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