PageSourceSearch

https://stahlin.com/modules/contrib/bootstrap_toast_messages/js/bootstrap-toast-messages.js?v=1.x

js stahlin.com collected 2026-09-29 09:25:58 UTC 15,062 bytes, 317 lines download raw bytes

1/**
2 * @file bootstrap_toast_messages.js
3 *
4 * Javascript for integrating the bootstrap toast JS messages into drupal
5 * Replaces the standard drupal status messages and creates bootstrap 4 toast instead
6 */
7(function ($, Drupal, once, drupalSettings) {
8
9  'use strict';
10
11  Drupal.bootstrap_toast_messages = {
12    id: 'drupal-bootstrap-toast-messages',
13    context: 'body',
14    bootstrapVersion: null,
15    defaultDuration: 10000,
16    animation: {
17      in: 'fade-in',
18      out: 'fade-out',
19      duration: 300,
20
21    },
22    messageTypeDuration: {
23      status: null,
24      success: null,
25      warning: null,
26    },
27    dataAttributes: {
28      dismiss: 'data-bs-dismiss',
29      delay: 'data-bs-delay',
30      autoHide: 'data-bs-autohide',
31      animation: 'data-bs-animation',
32    },
33    position: 'top-right', /** top-left/top-right/top-center/bottom-left/bottom-right/bottom-center - Where the toast will show up **/
34    pauseDelayOnHover: true, /** true/false - If you want to pause the delay of toast when hovering over the toast **/
35    defaultMessageType: 'status',
36    defaultMessageTitle: 'Info',
37    iconInfo: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/></svg>',
38    iconStatus: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#4ca154" class="bi bi-check2-circle mr-2" viewBox="0 0 16 16"><path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0"/><path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/></svg>',
39    iconWarning: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#cc7c2e" class="bi bi-exclamation-triangle mr-2" viewBox="0 0 16 16"><path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.15.15 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.2.2 0 0 1-.054.06.1.1 0 0 1-.066.017H1.146a.1.1 0 0 1-.066-.017.2.2 0 0 1-.054-.06.18.18 0 0 1 .002-.183L7.884 2.073a.15.15 0 0 1 .054-.057m1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767z"/><path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/></svg>',
40    iconError: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#ca3a31" class="bi bi-exclamation-octagon mr-2" viewBox="0 0 16 16"><path d="M4.54.146A.5.5 0 0 1 4.893 0h6.214a.5.5 0 0 1 .353.146l4.394 4.394a.5.5 0 0 1 .146.353v6.214a.5.5 0 0 1-.146.353l-4.394 4.394a.5.5 0 0 1-.353.146H4.893a.5.5 0 0 1-.353-.146L.146 11.46A.5.5 0 0 1 0 11.
40107V4.893a.5.5 0 0 1 .146-.353zM5.1 1 1 5.1v5.8L5.1 15h5.8l4.1-4.1V5.1L10.9 1z"/><path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/></svg>',
41    container: '<div id="toast-container" class="toast-container" aria-live="polite" aria-atomic="true"></div>',
42    messages: [],
43    count: 0,
44    totalRemovingHeight: 0,
45    init: function () {
46      this.detectVersion();
47      this.getSettings();
48      this.setAttributes();
49    },
50    detectVersion: function () {
51      if (typeof bootstrap === 'undefined' || typeof bootstrap.Toast === 'undefined') {
52        console.error('Bootstrap or bootstrap.Toast is not loaded.');
53        return;
54      }
55      if (bootstrap.Toast.VERSION.startsWith(4)) {
56        this.bootstrapVersion = 4;
57      } else if (bootstrap.Toast.VERSION.startsWith(5)) {
58        this.bootstrapVersion = 5;
59      } else {
60        console.error('Bootstrap Toast version ' + bootstrap.Toast.VERSION + ' is not supported.');
61      }
62      //console.log('Init() Bootstrap Toast Version', bootstrap.Toast.VERSION);
63    },
64    getSettings: function () {
65      if (typeof drupalSettings.bootstrap_toast_messages !== 'undefined') {
66        if (typeof drupalSettings.bootstrap_toast_messages.status_duration !== 'undefined') {
67          this.messageTypeDuration.status = drupalSettings.bootstrap_toast_messages.status_duration;
68        } else {
69          this.messageTypeDuration.status = this.defaultDuration;
70        }
71        if (typeof drupalSettings.bootstrap_toast_messages.warning_duration !== 'undefined') {
72          this.messageTypeDuration.warning = drupalSettings.bootstrap_toast_messages.warning_duration;
73        } else {
74          this.messageTypeDuration.warning = this.defaultDuration;
75        }
76        if (typeof drupalSettings.bootstrap_toast_messages.error_duration !== 'undefined') {
77          this.messageTypeDuration.error = drupalSettings.bootstrap_toast_messages.error_duration;
78        } else {
79          this.messageTypeDuration.error = this.defaultDuration;
80        }
81        if (typeof drupalSettings.bootstrap_toast_messages.position !== 'undefined') {
82          this.position = drupalSettings.bootstrap_toast_messages.position;
83        }
84        if (typeof drupalSettings.bootstrap_toast_messages.animation_in !== 'undefined') {
85          this.animation.in = drupalSettings.bootstrap_toast_messages.animation_in;
86        }
87        if (typeof drupalSettings.bootstrap_toast_messages.animation_out !== 'undefined') {
88          this.animation.out = drupalSettings.bootstrap_toast_messages.animation_out;
89        }
90      }
91    },
92    setAttributes: function () {
93      if (this.bootstrapVersion === 4) {
94        this.dataAttributes.dismiss = 'data-dismiss';
95        this.dataAttributes.delay = 'data-delay';
96        this.dataAttributes.autoHide = 'data-autohide';
97        this.dataAttributes.animation = 'data-animation';
98      }
99    },
100    createMessages: function (messages_array) {
101      if (!this.bootstrapVersion) {
102        console.error('Bootstrap version not detected.');
103        return;
104      }
105      //this.removeToasts();
106      this.messages = messages_array;
107      if (Array.isArray(this.messages) && this.messages.length) {
108        for (let i in this.messages) {
109          switch (this.messages[i].type) {
110            case 'status':
111            case 'success':
112              this.messages[i].type = 'success';
113              this.messages[i].drupalType = 'status';
114              this.messages[i].icon = this.iconStatus;
115              this.messages[i].title = 'Info';
116              break;
117            case 'warning':
118              this.messages[i].type = 'warning';
119              this.messages[i].drupalType = 'warning';
120              this.messages[i].icon = this.iconWarning;
121              this.messages[i].title = 'Warning';
122              break;
123            case 'error':
124            case 'danger':
125              this.messages[i].type = 'danger';
126              this.messages[i].drupalType = 'error';
127              this.messages[i].icon = this.iconError;
128              this.messages[i].title = 'Error';
129              break;
130            default:
131              this.messages[i].type = this.defaultMessageType;
132              this.messages[i].icon = this.iconInfo;
133              this.messages[i].title = this.defaultMessageTitle;
134          }
135        }
136        this.render();
137      }
138    },
139    render: function () {
140      let toastContainer = document.querySelector('#toast-container');
141      if (!toastContainer) {
142        toastContainer = document.createElement('div');
143        toastContainer.classList.add('toast-container', this.position, 'p-3');
144        toastContainer.id = 'toast-container';
145        toastContainer.setAttribute('role', 'alert');
146        toastContainer.setAttribute('aria-live', 'polite');
147        toastContainer.setAttribute('aria-atomic', 'true');
148        $('body').prepend(toastContainer);
149      }
150      for (let i in this.messages) {
151        toastContainer.appendChild(this.renderItem(this.messages[i]));
152      }
153      const toastElements = $('.toast');
154      switch (this.bootstrapVersion) {
155        case 4:
156          toastElements.toast('show');
157          break;
158        case 5:
159          const toastElementsArray = Array.from(toastElements);
160          toastElementsArray.forEach(function (toastElement) {
161            new bootstrap.Toast(toastElement).show();
162          });
163      }
164      toastElements.off('hide.bs.toast').on('hide.bs.toast', function () {
165        Drupal.bootstrap_toast_messages.onToastHide(this);
166      });
167    },
168    renderItem: function (message) {
169      let toast = document.createElement('div');
170      toast.classList.add('toast');
171      toast.id = 'toast-' + this.count;
172      toast.setAttribute('role', 'alert');
173      toast.setAttribute('aria-live', 'assertive');
174      toast.setAttribute('aria-atomic', 'true');
175      //Animation
176      toast.setAttribute(this.dataAttributes.animation, 'false'); //Disable bootstrap animations for custom animations
177      if (this.animation.in !== 'none' || this.animation.out !== 'none') {
178        toast.classList.add('animate');
179        if (this.animation.in !== 'none') {
180          toast.classList.add('animate-in', this.animation.in);
181        }
182        if (this.animation.out !== 'none') {
183          toast.classList.add('animate-out', this.animation.out);
184        }
185        if (this.messages.length > 1) {
186          toast.classList.add('animate-in--' + this.count);
187        }
188      }
189      //Bootstrap hide attributes
190      if (this.messageTypeDuration[message.drupalType] === 0) {
191        toast.setAttribute(this.dataAttributes.autoHide, 'false');
192      } else {
193        toast.setAttribute(this.dataAttributes.autoHide, 'true');
194        toast.setAttribute(this.dataAttributes.delay, this.messageTypeDuration[message.drupalType]);
195      }
196
197      let toastHeader = document.createElement('div');
198      toastHeader.classList.add('toast-header', 'bg-' + message.type);
199      toast.appendChild(toastHeader);
200
201      let toastBody = document.createElement('div');
202      toastBody.classList.add('toast-body', 'rounded-bottom', 'p-4', 'd-flex', 'align-items-center');
203      toastBody.innerHTML = '' +
204        '<span class="toast__icon pr-2 pe-2">' + message.icon + '</span>' +
205        '<span class="toast__message">' + message.message + '</span>' +
206        '<button type="button" class="ml-2 me-2 mb-1 ms-auto ml-auto toast__close" ' + this.dataAttributes.dismiss + '="toast" aria-label="Close"><svg width="9" height="9" viewBox="0 0 44 44" aria-hidden="true" focusable="false">
206<path d="M0.549989 4.44999L4.44999 0.549988L43.45 39.55L39.55 43.45L0.549989 4.44999Z" /><path d="M39.55 0.549988L43.45 4.44999L4.44999 43.45L0.549988 39.55L39.55 0.549988Z" /></svg></button>';
207      toast.appendChild(toastBody);
208      this.count++;
209      return toast;
210    },
211    removeToasts: function () {
212      let toasts = $('#toast-container').find('.toast');
213      if (toasts.length) {
214        toasts.each(function (index, toast) {
215          if (toast.classList.contains('hide')) {
216            toast.remove();
217          }
218        });
219      }
220    },
221    onToastHide(toast) {
222      if (!toast.classList.contains('animate-out')) {
223        toast.remove();
224      } else {
225        toast.classList.remove('animate-in');
226        toast.classList.add('removing');
227        let animationDuration = getComputedStyle(toast).animationDuration;
228        if (!animationDuration) {
229          animationDuration = '500ms';
230        }
231        let timeout = 0;
232        // Convert the animation duration to milliseconds
233        if (animationDuration.includes('ms')) {
234          timeout = parseFloat(animationDuration); // Already in milliseconds
235        } else if (animationDuration.includes('s')) {
236          timeout = parseFloat(animationDuration) * 1000; // Convert seconds to milliseconds
237        }
238
239        // Get the container as jQuery & its dimensions
240        const $container = $(toast.parentNode);
241        const containerHeight = $container.height();
242        const containerOuterHeight = $container.outerHeight(true);
243
244        //Get the toast as jQuery & its dimensions
245        const $toast = $(toast);
246        const toastOuterHeight = $toast.outerHeight(true);
247        const toastHeight = $toast.height();
248        const toastMarginBottom = $toast.css('margin-bottom');
249
250        this.totalRemovingHeight += toastOuterHeight;
251
252        let $next = null;
253        let $prev = null;
254        if (this.position.startsWith('top')) {
255          $next = $toast.next();
256        } else if (this.position.startsWith('bottom')) {
257          $prev = $toast.prev();
258        }
259        if (($next && $next.length) || ($prev && $prev.length)) {
260          $container.height(containerHeight);
261        }
262
263        // Set a timeout to remove the element after the animation duration
264        setTimeout(() => {
265          toast.remove();
266          //Top positioned toasts
267          if ($next && $next.length) {
268            $next.css({ 'margin-top': toastHeight });
269            $next.animate({ 'margin-top': 0 }, this.animation.duration, 'swing', function () {
270              $container.css('height', '');
271              //Not strictly required here but resetting the totalRemovingHeight
272              Drupal.bootstrap_toast_messages.totalRemovingHeight = 0;
273            });
274          }
275          //Bottom positioned toasts
276          else if ($prev && $prev.length) {
277            const removingCount = $('.toast-container .toast.removing').length;
278            $prev.css({ 'margin-bottom': toastHeight });
279            $prev.animate({ 'margin-bottom': toastMarginBottom }, this.animation.duration, 'swing', function () {
280              if(removingCount === 0) {
281                $container.animate({ height: (containerOuterHeight - Drupal.bootstrap_toast_messages.totalRemovingHeight) }, Drupal.bootstrap_toast_messages.animation.duration, 'swing', function () {
282                  $container.css('height', '');
283                  Drupal.bootstrap_toast_messages.totalRemovingHeight = 0;
284                });
285              }
286            });
287          }
288        }, Number(timeout));
289
290      }
291      this.count--;
292    },
293  };
294
295  /**
296   * Registers behaviours
297   */
298  Drupal.behaviors.bootstrap_toast_messages = {
299    attach: function (context, settings) {
300      once(Drupal.bootstrap_toast_messages.id, Drupal.bootstrap_toast_messages.context, context).forEach(function (element) {
301        Drupal.bootstrap_toast_messages.init();
302        if (typeof settings.bootstrap_toast_messages !== 'undefined' && typeof settings.bootstrap_toast_messages.messages_array !== 'undefined') {
303          Drupal.bootstrap_toast_messages.createMessages(settings.bootstrap_toast_messages.messages_array);
304          settings.bootstrap_toast_messages.messages_array = null;
305        }
306      });
307    },
308  };
309
310  /**
311   * Create bootstrap toast messages on ajax requests
312   */
313  Drupal.AjaxCommands.prototype.bootstrapToastMessagesCommand = function (ajax, response, status) {
314    Drupal.bootstrap_toast_messages.createMessages(response.messages_array);
315  };
316
317}(jQuery, Drupal, once, drupalSettings));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.