exports.notifications = (function() { // Milliseconds to fade in or out const fadeDuration = 250; function show({ html='', action='', deps=[], duration=5000, id=null, closeButton=true, type=null } = {}) { if (!id) id = randomId(); // Create panel if necessary _createPanel(); // Get existing DOM element for this ID, or create if needed. let $notification = _get(id); if ($notification.length === 0) $notification = _create(id); // Render html and dependencies const newHtml = `
${html}
` + `
${action}
`; const $content = $notification.find('.shiny-notification-content'); exports.renderContent($content, { html: newHtml, deps: deps }); // Remove any existing classes of the form 'shiny-notification-xxxx'. // The xxxx would be strings like 'warning'. const classes = $notification.attr('class') .split(/\s+/) .filter(cls => cls.match(/^shiny-notification-/)) .join(' '); $notification.removeClass(classes); // Add class. 'default' means no additional CSS class. if (type && type !== 'default') $notification.addClass('shiny-notification-' + type); // Make sure that the presence/absence of close button matches with value // of `closeButton`. const $close = $notification.find('.shiny-notification-close'); if (closeButton && $close.length === 0) { $notification.append('
×
'); } else if (!closeButton && $close.length !== 0) { $close.remove(); } // If duration was provided, schedule removal. If not, clear existing // removal callback (this happens if a message was first added with // a duration, and then updated with no duration). if (duration) _addRemovalCallback(id, duration); else _clearRemovalCallback(id); return id; } function remove(id) { _get(id).fadeOut(fadeDuration, function() { exports.unbindAll(this); $(this).remove(); // If no more notifications, remove the panel from the DOM. if (_ids().length === 0) { _getPanel().remove(); } }); } // Returns an individual notification DOM object (wrapped in jQuery). function _get(id) { if (!id) return null; return _getPanel().find('#shiny-notification-' + $escape(id)); } // Return array of all notification IDs function _ids() { return _getPanel() .find('.shiny-notification') .map(function() { return this.id.replace(/shiny-notification-/, ''); }) .get(); } // Returns the notification panel DOM object (wrapped in jQuery). function _getPanel() { return $('#shiny-notification-panel'); } // Create notifications panel and return the jQuery object. If the DOM // element already exists, just return it. function _createPanel() { let $panel = _getPanel(); if ($panel.length > 0) return $panel; $('body').append('
'); return $panel; } // Create a notification DOM element and return the jQuery object. If the // DOM element already exists for the ID, just return it without creating. function _create(id) { let $notification = _get(id); if ($notification.length === 0) { $notification = $( `
` + '
×
' + '
' + '
' ); $notification.find('.shiny-notification-close').on('click', e => { e.preventDefault(); e.stopPropagation(); remove(id); }); _getPanel().append($notification); } return $notification; } // Add a callback to remove a notification after a delay in ms. function _addRemovalCallback(id, delay) { // If there's an existing removalCallback, clear it before adding the new // one. _clearRemovalCallback(id); // Attach new removal callback const removalCallback = setTimeout(function() { remove(id); }, delay); _get(id).data('removalCallback', removalCallback); } // Clear a removal callback from a notification, if present. function _clearRemovalCallback(id) { const $notification = _get(id); const oldRemovalCallback = $notification.data('removalCallback'); if (oldRemovalCallback) { clearTimeout(oldRemovalCallback); } } return { show, remove }; })();