bootstrap.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /**
  2. * @file
  3. * bootstrap.js
  4. *
  5. * Provides general enhancements and fixes to Bootstrap's JS files.
  6. */
  7. var Drupal = Drupal || {};
  8. (function($, Drupal){
  9. "use strict";
  10. var $document = $(document);
  11. Drupal.behaviors.bootstrap = {
  12. attach: function(context) {
  13. // Provide some Bootstrap tab/Drupal integration.
  14. $(context).find('.tabbable').once('bootstrap-tabs', function () {
  15. var $wrapper = $(this);
  16. var $tabs = $wrapper.find('.nav-tabs');
  17. var $content = $wrapper.find('.tab-content');
  18. var borderRadius = parseInt($content.css('borderBottomRightRadius'), 10);
  19. var bootstrapTabResize = function() {
  20. if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) {
  21. $content.css('min-height', $tabs.outerHeight());
  22. }
  23. };
  24. // Add min-height on content for left and right tabs.
  25. bootstrapTabResize();
  26. // Detect tab switch.
  27. if ($wrapper.hasClass('tabs-left') || $wrapper.hasClass('tabs-right')) {
  28. $tabs.on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
  29. bootstrapTabResize();
  30. if ($wrapper.hasClass('tabs-left')) {
  31. if ($(e.target).parent().is(':first-child')) {
  32. $content.css('borderTopLeftRadius', '0');
  33. }
  34. else {
  35. $content.css('borderTopLeftRadius', borderRadius + 'px');
  36. }
  37. }
  38. else {
  39. if ($(e.target).parent().is(':first-child')) {
  40. $content.css('borderTopRightRadius', '0');
  41. }
  42. else {
  43. $content.css('borderTopRightRadius', borderRadius + 'px');
  44. }
  45. }
  46. });
  47. }
  48. });
  49. }
  50. };
  51. /**
  52. * Behavior for .
  53. */
  54. Drupal.behaviors.bootstrapFormHasError = {
  55. attach: function (context, settings) {
  56. if (settings.bootstrap && settings.bootstrap.formHasError) {
  57. var $context = $(context);
  58. $context.find('.form-item.has-error:not(.form-type-password.has-feedback)').once('error', function () {
  59. var $formItem = $(this);
  60. var $input = $formItem.find(':input');
  61. $input.on('keyup focus blur', function () {
  62. var value = $input.val() || false;
  63. $formItem[value ? 'removeClass' : 'addClass']('has-error');
  64. $input[value ? 'removeClass' : 'addClass']('error');
  65. });
  66. });
  67. }
  68. }
  69. };
  70. /**
  71. * Bootstrap Popovers.
  72. */
  73. Drupal.behaviors.bootstrapPopovers = {
  74. attach: function (context, settings) {
  75. // Immediately return if popovers are not available.
  76. if (!settings.bootstrap || !settings.bootstrap.popoverEnabled || !$.fn.popover) {
  77. return;
  78. }
  79. // Popover autoclose.
  80. if (settings.bootstrap.popoverOptions.triggerAutoclose) {
  81. var $currentPopover = null;
  82. $document
  83. .on('show.bs.popover', '[data-toggle=popover]', function () {
  84. var $trigger = $(this);
  85. var popover = $trigger.data('bs.popover');
  86. // Only keep track of clicked triggers that we're manually handling.
  87. if (popover.options.originalTrigger === 'click') {
  88. if ($currentPopover && !$currentPopover.is($trigger)) {
  89. $currentPopover.popover('hide');
  90. }
  91. $currentPopover = $trigger;
  92. }
  93. })
  94. .on('click', function (e) {
  95. var $target = $(e.target);
  96. var popover = $target.is('[data-toggle=popover]') && $target.data('bs.popover');
  97. if ($currentPopover && !$target.is('[data-toggle=popover]') && !$target.closest('.popover.in')[0]) {
  98. $currentPopover.popover('hide');
  99. $currentPopover = null;
  100. }
  101. })
  102. ;
  103. }
  104. var elements = $(context).find('[data-toggle=popover]').toArray();
  105. for (var i = 0; i < elements.length; i++) {
  106. var $element = $(elements[i]);
  107. var options = $.extend({}, $.fn.popover.Constructor.DEFAULTS, settings.bootstrap.popoverOptions, $element.data());
  108. // Store the original trigger.
  109. options.originalTrigger = options.trigger;
  110. // If the trigger is "click", then we'll handle it manually here.
  111. if (options.trigger === 'click') {
  112. options.trigger = 'manual';
  113. }
  114. // Retrieve content from a target element.
  115. var target = options.target || $element.is('a[href^="#"]') && $element.attr('href');
  116. var $target = $document.find(target).clone();
  117. if (!options.content && $target[0]) {
  118. $target.removeClass('element-invisible hidden').removeAttr('aria-hidden');
  119. options.content = $target.wrap('<div/>').parent()[options.html ? 'html' : 'text']() || '';
  120. }
  121. // Initialize the popover.
  122. $element.popover(options);
  123. // Handle clicks manually.
  124. if (options.originalTrigger === 'click') {
  125. // To ensure the element is bound multiple times, remove any
  126. // previously set event handler before adding another one.
  127. $element
  128. .off('click.drupal.bootstrap.popover')
  129. .on('click.drupal.bootstrap.popover', function (e) {
  130. $(this).popover('toggle');
  131. e.preventDefault();
  132. e.stopPropagation();
  133. })
  134. ;
  135. }
  136. }
  137. },
  138. detach: function (context, settings) {
  139. // Immediately return if popovers are not available.
  140. if (!settings.bootstrap || !settings.bootstrap.popoverEnabled || !$.fn.popover) {
  141. return;
  142. }
  143. // Destroy all popovers.
  144. $(context).find('[data-toggle="popover"]')
  145. .off('click.drupal.bootstrap.popover')
  146. .popover('destroy')
  147. ;
  148. }
  149. };
  150. /**
  151. * Bootstrap Tooltips.
  152. */
  153. Drupal.behaviors.bootstrapTooltips = {
  154. attach: function (context, settings) {
  155. if (settings.bootstrap && settings.bootstrap.tooltipEnabled) {
  156. var elements = $(context).find('[data-toggle="tooltip"]').toArray();
  157. for (var i = 0; i < elements.length; i++) {
  158. var $element = $(elements[i]);
  159. var options = $.extend({}, settings.bootstrap.tooltipOptions, $element.data());
  160. $element.tooltip(options);
  161. }
  162. }
  163. }
  164. };
  165. /**
  166. * Anchor fixes.
  167. */
  168. var $scrollableElement = $();
  169. Drupal.behaviors.bootstrapAnchors = {
  170. attach: function(context, settings) {
  171. var i, elements = ['html', 'body'];
  172. if (!$scrollableElement.length) {
  173. for (i = 0; i < elements.length; i++) {
  174. var $element = $(elements[i]);
  175. if ($element.scrollTop() > 0) {
  176. $scrollableElement = $element;
  177. break;
  178. }
  179. else {
  180. $element.scrollTop(1);
  181. if ($element.scrollTop() > 0) {
  182. $element.scrollTop(0);
  183. $scrollableElement = $element;
  184. break;
  185. }
  186. }
  187. }
  188. }
  189. if (!settings.bootstrap || settings.bootstrap.anchorsFix !== '1') {
  190. return;
  191. }
  192. var anchors = $(context).find('a').toArray();
  193. for (i = 0; i < anchors.length; i++) {
  194. if (!anchors[i].scrollTo) {
  195. this.bootstrapAnchor(anchors[i]);
  196. }
  197. }
  198. $scrollableElement.once('bootstrap-anchors', function () {
  199. $scrollableElement.on('click.bootstrap-anchors', 'a[href*="#"]:not([data-toggle],[data-target],[data-slide])', function(e) {
  200. if (this.scrollTo) {
  201. this.scrollTo(e);
  202. }
  203. });
  204. });
  205. },
  206. bootstrapAnchor: function (element) {
  207. element.validAnchor = element.nodeName === 'A' && (location.hostname === element.hostname || !element.hostname) && (element.hash.replace(/#/,'').length > 0);
  208. element.scrollTo = function(event) {
  209. var attr = 'id';
  210. var $target = $(element.hash);
  211. // Check for anchors that use the name attribute instead.
  212. if (!$target.length) {
  213. attr = 'name';
  214. $target = $('[name="' + element.hash.replace('#', '') + '"]');
  215. }
  216. // Immediately stop if no anchors are found.
  217. if (!this.validAnchor && !$target.length) {
  218. return;
  219. }
  220. // Anchor is valid, continue if there is an offset.
  221. var offset = $target.offset().top - parseInt($scrollableElement.css('paddingTop'), 10) - parseInt($scrollableElement.css('marginTop'), 10);
  222. if (offset > 0) {
  223. if (event) {
  224. event.preventDefault();
  225. }
  226. var $fakeAnchor = $('<div/>')
  227. .addClass('element-invisible')
  228. .attr(attr, $target.attr(attr))
  229. .css({
  230. position: 'absolute',
  231. top: offset + 'px',
  232. zIndex: -1000
  233. })
  234. .appendTo($scrollableElement);
  235. $target.removeAttr(attr);
  236. var complete = function () {
  237. location.hash = element.hash;
  238. $fakeAnchor.remove();
  239. $target.attr(attr, element.hash.replace('#', ''));
  240. };
  241. if (Drupal.settings.bootstrap.anchorsSmoothScrolling) {
  242. $scrollableElement.animate({ scrollTop: offset, avoidTransforms: true }, 400, complete);
  243. }
  244. else {
  245. $scrollableElement.scrollTop(offset);
  246. complete();
  247. }
  248. }
  249. };
  250. }
  251. };
  252. /**
  253. * Tabledrag theming elements.
  254. */
  255. Drupal.theme.tableDragChangedMarker = function () {
  256. return '<span class="tabledrag-changed glyphicon glyphicon-warning-sign text-warning"></span>';
  257. };
  258. Drupal.theme.tableDragChangedWarning = function () {
  259. return '<div class="tabledrag-changed-warning alert alert-warning messages warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('Changes made in this table will not be saved until the form is submitted.') + '</div>';
  260. };
  261. })(jQuery, Drupal);