| 1 | /** |
| 2 | * @file |
| 3 | * Bootstrap Popovers. |
| 4 | */ |
| 5 | |
| 6 | var Drupal = Drupal || {}; |
| 7 | |
| 8 | (function ($, Drupal, Bootstrap) { |
| 9 | "use strict"; |
| 10 | |
| 11 | var $document = $(document); |
| 12 | |
| 13 | /** |
| 14 | * Extend the Bootstrap Popover plugin constructor class. |
| 15 | */ |
| 16 | Bootstrap.extendPlugin('popover', function (settings) { |
| 17 | return { |
| 18 | DEFAULTS: { |
| 19 | animation: !!settings.popover_animation, |
| 20 | autoClose: !!settings.popover_auto_close, |
| 21 | enabled: settings.popover_enabled, |
| 22 | html: !!settings.popover_html, |
| 23 | placement: settings.popover_placement, |
| 24 | selector: settings.popover_selector, |
| 25 | trigger: settings.popover_trigger, |
| 26 | title: settings.popover_title, |
| 27 | content: settings.popover_content, |
| 28 | delay: parseInt(settings.popover_delay, 10), |
| 29 | container: settings.popover_container |
| 30 | } |
| 31 | }; |
| 32 | }); |
| 33 | |
| 34 | /** |
| 35 | * Bootstrap Popovers. |
| 36 | * |
| 37 | * @todo This should really be properly delegated if selector option is set. |
| 38 | */ |
| 39 | Drupal.behaviors.bootstrapPopovers = { |
| 40 | $activePopover: null, |
| 41 | attach: function (context) { |
| 42 | // Immediately return if popovers are not available. |
| 43 | if (!$.fn.popover || !$.fn.popover.Constructor.DEFAULTS.enabled) { |
| 44 | return; |
| 45 | } |
| 46 | |
| 47 | var _this = this; |
| 48 | |
| 49 | $document |
| 50 | .on('show.bs.popover', '[data-toggle=popover]', function () { |
| 51 | var $trigger = $(this); |
| 52 | var popover = $trigger.data('bs.popover'); |
| 53 | |
| 54 | // Only keep track of clicked triggers that we're manually handling. |
| 55 | if (popover.options.originalTrigger === 'click') { |
| 56 | if (_this.$activePopover && _this.getOption('autoClose') && !_this.$activePopover.is($trigger)) { |
| 57 | _this.$activePopover.popover('hide'); |
| 58 | } |
| 59 | _this.$activePopover = $trigger; |
| 60 | } |
| 61 | }) |
| 62 | // Unfortunately, :focusable is only made available when using jQuery |
| 63 | // UI. While this would be the most semantic pseudo selector to use |
| 64 | // here, jQuery UI may not always be loaded. Instead, just use :visible |
| 65 | // here as this just needs some sort of selector here. This activates |
| 66 | // delegate binding to elements in jQuery so it can work it's bubbling |
| 67 | // focus magic since elements don't really propagate their focus events. |
| 68 | // @see https://www.drupal.org/project/bootstrap/issues/3013236 |
| 69 | .on('focus.bs.popover', ':visible', function (e) { |
| 70 | var $target = $(e.target); |
| 71 | if (_this.$activePopover && _this.getOption('autoClose') && !_this.$activePopover.is($target) && !$target.closest('.popover.in')[0]) { |
| 72 | _this.$activePopover.popover('hide'); |
| 73 | _this.$activePopover = null; |
| 74 | } |
| 75 | }) |
| 76 | .on('click.bs.popover', function (e) { |
| 77 | var $target = $(e.target); |
| 78 | if (_this.$activePopover && _this.getOption('autoClose') && !$target.is('[data-toggle=popover]') && !$target.closest('.popover.in')[0]) { |
| 79 | _this.$activePopover.popover('hide'); |
| 80 | _this.$activePopover = null; |
| 81 | } |
| 82 | }) |
| 83 | .on('keyup.bs.popover', function (e) { |
| 84 | if (_this.$activePopover && _this.getOption('autoClose') && e.which === 27) { |
| 85 | _this.$activePopover.popover('hide'); |
| 86 | _this.$activePopover = null; |
| 87 | } |
| 88 | }) |
| 89 | ; |
| 90 | |
| 91 | var elements = $(context).find('[data-toggle=popover]').toArray(); |
| 92 | for (var i = 0; i < elements.length; i++) { |
| 93 | var $element = $(elements[i]); |
| 94 | var options = $.extend({}, $.fn.popover.Constructor.DEFAULTS, $element.data()); |
| 95 | |
| 96 | // Store the original trigger. |
| 97 | options.originalTrigger = options.trigger; |
| 98 | |
| 99 | // If the trigger is "click", then we'll handle it manually here. |
| 100 | if (options.trigger === 'click') { |
| 101 | options.trigger = 'manual'; |
| 102 | } |
| 103 | |
| 104 | // Retrieve content from a target element. |
| 105 | var target = options.target || $element.is('a[href^="#"]') && $element.attr('href'); |
| 106 | var $target = $document.find(target).clone(); |
| 107 | if (!options.content && $target[0]) { |
| 108 | $target.removeClass('visually-hidden hidden').removeAttr('aria-hidden'); |
| 109 | options.content = $target.wrap('<div/>').parent()[options.html ? 'html' : 'text']() || ''; |
| 110 | } |
| 111 | |
| 112 | // Initialize the popover. |
| 113 | $element.popover(options); |
| 114 | |
| 115 | // Handle clicks manually. |
| 116 | if (options.originalTrigger === 'click') { |
| 117 | // To ensure the element is bound multiple times, remove any |
| 118 | // previously set event handler before adding another one. |
| 119 | $element |
| 120 | .off('click.drupal.bootstrap.popover') |
| 121 | .on('click.drupal.bootstrap.popover', function (e) { |
| 122 | $(this).popover('toggle'); |
| 123 | e.preventDefault(); |
| 124 | e.stopPropagation(); |
| 125 | }) |
| 126 | ; |
| 127 | } |
| 128 | } |
| 129 | }, |
| 130 | detach: function (context) { |
| 131 | // Immediately return if popovers are not available. |
| 132 | if (!$.fn.popover || !$.fn.popover.Constructor.DEFAULTS.enabled) { |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | // Destroy all popovers. |
| 137 | $(context).find('[data-toggle="popover"]') |
| 138 | .off('click.drupal.bootstrap.popover') |
| 139 | .popover('destroy') |
| 140 | ; |
| 141 | }, |
| 142 | getOption: function(name, defaultValue, element) { |
| 143 | var $element = element ? $(element) : this.$activePopover; |
| 144 | var options = $.extend(true, {}, $.fn.popover.Constructor.DEFAULTS, ($element && $element.data('bs.popover') || {}).options); |
| 145 | if (options[name] !== void 0) { |
| 146 | return options[name]; |
| 147 | } |
| 148 | return defaultValue !== void 0 ? defaultValue : void 0; |
| 149 | } |
| 150 | }; |
| 151 | |
| 152 | })(window.jQuery, window.Drupal, window.Drupal.bootstrap); |