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.