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