horizontal-tabs.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. (function ($) {
  2. /**
  3. * This script transforms a set of fieldsets into a stack of horizontal
  4. * tabs. Another tab pane can be selected by clicking on the respective
  5. * tab.
  6. *
  7. * Each tab may have a summary which can be updated by another
  8. * script. For that to work, each fieldset has an associated
  9. * 'horizontalTabCallback' (with jQuery.data() attached to the fieldset),
  10. * which is called every time the user performs an update to a form
  11. * element inside the tab pane.
  12. */
  13. Drupal.behaviors.horizontalTabs = {
  14. attach: function (context) {
  15. $('.horizontal-tabs-panes', context).once('horizontal-tabs', function () {
  16. var focusID = $(':hidden.horizontal-tabs-active-tab', this).val();
  17. var tab_focus;
  18. // Check if there are some fieldsets that can be converted to horizontal-tabs
  19. var $fieldsets = $('> fieldset', this);
  20. if ($fieldsets.length == 0) {
  21. return;
  22. }
  23. // Create the tab column.
  24. var tab_list = $('<ul class="horizontal-tabs-list"></ul>');
  25. $(this).wrap('<div class="horizontal-tabs clearfix"></div>').before(tab_list);
  26. // Transform each fieldset into a tab.
  27. $fieldsets.each(function (i) {
  28. var $legend = $('> legend', this);
  29. $('.element-invisible', $legend).remove();
  30. var horizontal_tab = new Drupal.horizontalTab({
  31. title: $legend.text(),
  32. fieldset: $(this)
  33. });
  34. horizontal_tab.item.addClass('horizontal-tab-button-' + i);
  35. tab_list.append(horizontal_tab.item);
  36. $(this)
  37. .removeClass('collapsible collapsed')
  38. .addClass('horizontal-tabs-pane')
  39. .data('horizontalTab', horizontal_tab);
  40. if (this.id == focusID) {
  41. tab_focus = $(this);
  42. }
  43. });
  44. $('> li:first', tab_list).addClass('first');
  45. $('> li:last', tab_list).addClass('last');
  46. if (!tab_focus) {
  47. // If the current URL has a fragment and one of the tabs contains an
  48. // element that matches the URL fragment, activate that tab.
  49. var hash = window.location.hash.replace(/[=%;,\/]/g, "");
  50. if (hash !== '#' && $(hash, this).length) {
  51. tab_focus = $(hash, this).closest('.horizontal-tabs-pane');
  52. }
  53. else {
  54. tab_focus = $('> .horizontal-tabs-pane:first', this);
  55. }
  56. }
  57. if (tab_focus.length) {
  58. tab_focus.data('horizontalTab').focus();
  59. }
  60. });
  61. }
  62. };
  63. /**
  64. * The horizontal tab object represents a single tab within a tab group.
  65. *
  66. * @param settings
  67. * An object with the following keys:
  68. * - title: The name of the tab.
  69. * - fieldset: The jQuery object of the fieldset that is the tab pane.
  70. */
  71. Drupal.horizontalTab = function (settings) {
  72. var self = this;
  73. $.extend(this, settings, Drupal.theme('horizontalTab', settings));
  74. this.link.click(function () {
  75. self.focus();
  76. return false;
  77. });
  78. // Keyboard events added:
  79. // Pressing the Enter key will open the tab pane.
  80. this.link.keydown(function(event) {
  81. if (event.keyCode == 13) {
  82. self.focus();
  83. // Set focus on the first input field of the visible fieldset/tab pane.
  84. $("fieldset.horizontal-tabs-pane :input:visible:enabled:first").focus();
  85. return false;
  86. }
  87. });
  88. // Only bind update summary on forms.
  89. if (this.fieldset.drupalGetSummary) {
  90. this.fieldset.bind('summaryUpdated', function() {
  91. self.updateSummary();
  92. }).trigger('summaryUpdated');
  93. }
  94. };
  95. Drupal.horizontalTab.prototype = {
  96. /**
  97. * Displays the tab's content pane.
  98. */
  99. focus: function () {
  100. this.fieldset
  101. .removeClass('horizontal-tab-hidden')
  102. .siblings('fieldset.horizontal-tabs-pane')
  103. .each(function () {
  104. var tab = $(this).data('horizontalTab');
  105. tab.fieldset.addClass('horizontal-tab-hidden');
  106. tab.item.removeClass('selected');
  107. })
  108. .end()
  109. .siblings(':hidden.horizontal-tabs-active-tab')
  110. .val(this.fieldset.attr('id'));
  111. this.item.addClass('selected');
  112. // Mark the active tab for screen readers.
  113. $('#active-horizontal-tab').remove();
  114. this.link.append('<span id="active-horizontal-tab" class="element-invisible">' + Drupal.t('(active tab)') + '</span>');
  115. },
  116. /**
  117. * Updates the tab's summary.
  118. */
  119. updateSummary: function () {
  120. this.summary.html(this.fieldset.drupalGetSummary());
  121. },
  122. /**
  123. * Shows a horizontal tab pane.
  124. */
  125. tabShow: function () {
  126. // Display the tab.
  127. this.item.removeClass('horizontal-tab-hidden');
  128. // Update .first marker for items. We need recurse from parent to retain the
  129. // actual DOM element order as jQuery implements sortOrder, but not as public
  130. // method.
  131. this.item.parent().children('.horizontal-tab-button').removeClass('first')
  132. .filter(':visible:first').addClass('first');
  133. // Display the fieldset.
  134. this.fieldset.removeClass('horizontal-tab-hidden');
  135. // Focus this tab.
  136. this.focus();
  137. return this;
  138. },
  139. /**
  140. * Hides a horizontal tab pane.
  141. */
  142. tabHide: function () {
  143. // Hide this tab.
  144. this.item.addClass('horizontal-tab-hidden');
  145. // Update .first marker for items. We need recurse from parent to retain the
  146. // actual DOM element order as jQuery implements sortOrder, but not as public
  147. // method.
  148. this.item.parent().children('.horizontal-tab-button').removeClass('first')
  149. .filter(':visible:first').addClass('first');
  150. // Hide the fieldset.
  151. this.fieldset.addClass('horizontal-tab-hidden');
  152. // Focus the first visible tab (if there is one).
  153. var $firstTab = this.fieldset.siblings('.horizontal-tabs-pane:not(.horizontal-tab-hidden):first');
  154. if ($firstTab.length) {
  155. $firstTab.data('horizontalTab').focus();
  156. }
  157. return this;
  158. }
  159. };
  160. /**
  161. * Theme function for a horizontal tab.
  162. *
  163. * @param settings
  164. * An object with the following keys:
  165. * - title: The name of the tab.
  166. * @return
  167. * This function has to return an object with at least these keys:
  168. * - item: The root tab jQuery element
  169. * - link: The anchor tag that acts as the clickable area of the tab
  170. * (jQuery version)
  171. * - summary: The jQuery element that contains the tab summary
  172. */
  173. Drupal.theme.prototype.horizontalTab = function (settings) {
  174. var tab = {};
  175. var idAttr = settings.fieldset.attr('id');
  176. tab.item = $('<li class="horizontal-tab-button" tabindex="-1"></li>')
  177. .append(tab.link = $('<a href="#' + idAttr + '"></a>')
  178. .append(tab.title = $('<strong></strong>').text(settings.title))
  179. );
  180. // No need to add summary on frontend.
  181. if (settings.fieldset.drupalGetSummary) {
  182. tab.link.append(tab.summary = $('<span class="summary"></span>'))
  183. }
  184. return tab;
  185. };
  186. })(jQuery);