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