Mercurial > hg > cmmr2012-drupal-site
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)); |