UNPKG

@userfrosting/theme-adminlte

Version:
285 lines (261 loc) 11 kB
/** * ufAlerts jQuery plugin. Fetches and renders alerts from the UF alert stream. * * Based on template from https://github.com/jquery-boilerplate/jquery-boilerplate * * === USAGE === * ufAlerts can be initialized on any container element as follows: * * $('#myDiv').ufAlerts(options); * * `options` is an object containing any of the following parameters: * @param {string} url The absolute URL from which to fetch flash alerts. * @param {bool} scrollToTop Whether to automatically scroll back to the top of the page after rendering alerts. * @param {string} alertMessageClass The CSS class(es) to be applied to each alert message. * @param {string} alertTemplateId The CSS id(es) for the Handlebar alert template. * @param {bool} agglomerate Set to true to render all alerts together, applying styling for the highest-priority alert being rendered. * * == EVENTS == * * uf-form triggers the following events: * * `fetch.ufAlerts`: triggered when the alerts have been successfully fetched from the server. * `render.ufAlerts`: triggered when all alerts have been rendered and the call to render() has completed. * * == METHODS == * * `fetch()`: Asynchronously gets alerts from the server. * `push(options)`: Adds a alert of a specified type (danger, warning, info, success) to the internal collection of alerts. * `clear()`: Removes all messages from the internal collection. * `render()`: Renders the collection of alerts to the container, awaiting results of `fetch()` if required. * * UserFrosting https://www.userfrosting.com * @author Alexander Weissman <https://alexanderweissman.com> */ ;(function($, window, document, undefined) { 'use strict'; // Define plugin name and defaults. var pluginName = 'ufAlerts', defaults = { url : site.uri.public + '/alerts', scrollToTop : true, scrollWhenVisible : false, agglomerate : false, alertMessageClass : 'uf-alert-message', alertTemplateId : 'uf-alert-template', DEBUG : false }; // Constructor function Plugin (element, options) { this.element = element[0]; this.$element = $(this.element); this.settings = $.extend(true, {}, defaults, options); this._defaults = defaults; this._name = pluginName; // Detect changes to element attributes this.$element.attrchange({ callback: function (event) { this.element = event.target; }.bind(this) }); // Plugin variables this.alerts = []; this._newAlertsPromise = $.Deferred().resolve(); this._alertTemplateHtml = $('#' + this.settings.alertTemplateId).html(); this._alertTypePriorities = { danger : 3, warning: 2, success: 1, info : 0 }; this._alertTypeIcon = { danger : 'fa-ban', warning: 'fa-warning', success: 'fa-check', info : 'fa-info' }; return this; } // Functions $.extend(Plugin.prototype, { /** * Clear all alerts from the current uf-alerts collection. */ clear: function() { // See http://stackoverflow.com/a/1232046/2970321 this.alerts.length = 0; if (this.settings.agglomerate) { this.element.toggleClass('alert', false) .toggleClass('alert-info', false) .toggleClass('alert-success', false) .toggleClass('alert-warning', false) .toggleClass('alert-danger', false); } // Clear any alert HTML this.$element.empty(); return this.$element; }, /** * Fetches alerts from the alert stream */ fetch: function() { // Set a promise, so that any chained calls after fetch can wait until the messages have been retrieved this._newAlertsPromise = $.ajax({ url: this.settings.url, cache: false }).then( // Success this._fetchSuccess.bind(this), // Failure this._fetchFailure.bind(this) ); return this.$element; }, /** * Success callback for fetch */ _fetchSuccess: function(alerts) { if (alerts != null) this.alerts = $.merge(this.alerts, alerts); this.$element.trigger('fetch.' + this._name); }, /** * Failure callback for fetch */ _fetchFailure: function(response) { this.$element.trigger('error.' + this._name); if ((typeof site !== 'undefined') && site.debug.ajax && response.responseText) { document.write(response.responseText); document.close(); } else { if (this.settings.DEBUG) { console.warn('Error (' + response.status + '): ' + response.responseText ); } } }, /** * Push a given message to the current uf-alerts collection. */ push: function(options) { this.alerts.push({ type : options[0], message: options[1] }); return this.$element; }, /** * Renders the alerts. */ render: function() { // Wait for promise completion, only if promise is unresolved. if (this._newAlertsPromise.state() == 'resolved' || this._newAlertsPromise.state() == 'rejected') { this._render(); } else { $.when(this._newAlertsPromise).then(this._render.bind(this)); } return this.$element; }, /* * Internal private method that physically handles rendering operation. */ _render: function() { // Holds generated HTML var alertHtml = ''; // Only compile alerts if there are alerts to display if (this.alerts.length > 0) { // Prepare template var alertTemplate = Handlebars.compile(this._alertTemplateHtml, {noEscape: true}); var i; // If agglomeration is enabled, set the container to the highest priority alert type if (this.settings.agglomerate) { // Holds generated agglomerated alerts var alertMessage = '<ul>'; // Determine overall alert priority var alertContainerType = 'info'; for (i = 0; i < this.alerts.length; i++) { if (this._alertTypePriorities[this.alerts[i].type] > this._alertTypePriorities[alertContainerType]) { alertContainerType = this.alerts[i].type; } } // Compile each alert var aggTemplate = Handlebars.compile('<li class=' + this.settings.alertMessageClass + '>{{ message }}</li>'); for (i = 0; i < this.alerts.length; i++) { alertMessage += aggTemplate(this.alerts[i]); } alertMessage += '</ul>'; // Generate complete alert HTML alertHtml = alertTemplate({ type : alertContainerType, message: alertMessage, icon : this._alertTypeIcon[alertContainerType] }); } else { // Compile each alert. for (i = 0; i < this.alerts.length; i++) { var alert = this.alerts[i]; // Inject icon alert.icon = this._alertTypeIcon[alert.type]; // Compile alert alertHtml += alertTemplate(alert); } } } // Show alerts this.$element.html(alertHtml); // Scroll to top of alert location is new alerts output, and auto scrolling is enabled if (this.settings.scrollToTop && alertHtml !== '') { // Don't scroll if already visible, unless scrollWhenVisible is true if (!this._alertsVisible() || this.settings.scrollWhenVisible) { $('html, body').animate({ scrollTop: this.$element.offset().top }, 'fast'); } } // Trigger render events this.$element.trigger('render.' + this._name); }, /** * Returns true if alerts container is completely within the viewport. */ _alertsVisible: function() { var rect = this.element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }, /** * Completely destroy the ufAlerts plugin on the element. */ destroy: function() { // Unbind any bound events this.$element.off('.' + this._name); // Remove plugin from element this.$element.removeData(this._name); return this.$element; } }); // Handles instantiation and access to non-private methods. $.fn[pluginName] = function(methodOrOptions) { // Grab plugin instance var instance = $(this).data(pluginName); // If undefined or object, initialize plugin. if (methodOrOptions === undefined || typeof methodOrOptions === 'object') { // Only initialize if not previously done. if (!instance) { $(this).data(pluginName, new Plugin(this, methodOrOptions)); } return this; } // Otherwise ensure first parameter is a valid string, and is the name of an actual function. else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') { // Ensure not a private function if (methodOrOptions.indexOf('_') !== 0) { return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1)); } else { console.warn('Method ' + methodOrOptions + ' is private!'); } } else { console.warn('Method ' + methodOrOptions + ' does not exist.'); } }; })(jQuery, window, document);