83cfec446da94836d42af3c0dcf6c3b8ffb9b477
[yaffs-website] / web / themes / contrib / bootstrap / js / misc / vertical-tabs.js
1 /**
2  * @file
3  * Overrides core/misc/vertical-tabs.js.
4  */
5
6 (function ($, window, Drupal, drupalSettings) {
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       $('body').once('vertical-tabs-fragments').on('formFragmentLinkClickOrHashChange.verticalTabs', handleFragmentLinkClickOrHashChange);
50
51       $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').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       $(context).find('.tabbable').once('bootstrap-tabs').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);