angular-extended-notifications
Version:
Highly customizable notifications with AngularJS
245 lines (204 loc) • 7.2 kB
JavaScript
/*!
* angular-extended-notifications v1.0.5 - 2016-05-06
* (c) 2014-2016 L.systems SARL, Etienne Folio, Quentin Raynaud
* https://bitbucket.org/lsystems/angular-extended-notifications
* License: MIT
*/
angular.module('angular-extended-notifications', []).
provider('notifications', function() {
'use strict';
// notifications.info(title, message, data);
// notifications.info(title, message, {faIcon: 'fa-heart'});
// notifications.info(data);
// notifications.error(…);
// notifications.success(…);
// notifications.warning(…);
// notifications.notify(data);
// Possible data options:
// ---------------------
//
// data = {
// actions: [{
// label: string
// fn: function
// }]
// closeButton: bool
// title: title of the message
// message: message
// webkitNotifications: {
// iconFile: icon file
// notificationUrl: url to load as a webkit notification base (replaces default behavior)
// }
// attachTo: angular/jquery element or function
// className: className to apply on the template's root element
// duration: int in ms
// faIcon: font-awesome faIcon classname (fa-info for example)
// template: filename without extension
// templateFile: filename
// templatesDir: path
// show: function
// close: function
// }
var faIcons = {
info: 'fa-info',
error: 'fa-exclamation-circle',
success: 'fa-check',
warning: 'fa-exclamation-triangle'
};
this.setFaIcons = function(map) {
for (var i in map)
faIcons[i] = map[i];
return this;
};
var defaults = {
attachTo: 'body',
className: '',
duration: 2500,
webkitNotifications: false,
close: angular.noop,
show: angular.noop,
closeButton: true,
templateFile: 'global-notification.html',
templatesDir: 'templates/'
};
this.setDefaults = function(def) {
for (var i in def)
defaults[i] = def[i];
return this;
};
this.$get = ['$timeout', '$templateCache', '$injector', '$rootScope', '$compile', '$q',
function($timeout, $templateCache, $injector, $rootScope, $compile, $q) {
var queue = [];
function notify(data) {
var notif;
// complete data with default values
for (var i in defaults)
if (data[i] === undefined)
data[i] = defaults[i];
if (data.template)
data.templateFile = data.template + '.html';
var wkn = window.webkitNotifications;
// message & title may be promises, let's resolve them
var promise = $q
.all([
$q.when(data.message),
$q.when(data.title)
])
.then(function(d) {
data.message = d[0];
data.title = d[1];
// webkitNotifications mode
if (data.webkitNotifications && wkn && !wkn.checkPermission()) {
if (typeof data.webkitNotifications !== 'object')
throw new Error('data.webkitNotifications should either be false or an object');
if (data.webkitNotifications.notificationUrl)
// TODO: add data object as url params (use url module from node)
notif = wkn.createHTMLNotification(data.webkitNotifications.notificationUrl);
else
notif = wkn.createNotification(
data.webkitNotifications.iconFile,
data.title,
data.content
);
notif.ondisplay = data.show;
notif.close = data.close;
notif.show();
return notif;
}
});
// wait until promises are resolved for webkitNotifications
if (data.webkitNotifications && wkn && !wkn.checkPermission())
return promise;
var showNotif = function(template) {
// create scope & copy data elements in it
var scope = $rootScope.$new(true);
scope.close = notif.close.bind(notif);
scope.data = data;
// compile template element
notif.templateElement = $compile(template)(scope);
// add className
notif.templateElement.addClass(data.className);
// get element from string
if (typeof data.attachTo === 'string')
data.attachTo = angular.element(document.querySelector(data.attachTo));
// attach it on the DOM
if (typeof data.attachTo === 'object' && data.attachTo.prepend)
data.attachTo.prepend(notif.templateElement);
else if (typeof data.attachTo === 'function')
data.attachTo(notif.templateElement);
else
throw new Error('Invalid value for attachTo. It should be either a function ' +
'or an object with a prepend method');
// add it to the notifications queue
queue.push(notif);
if (data.closeOnRouteChange) {
if (!angular.isString(data.closeOnRouteChange))
throw new Error('Invalid property closeOnRouteChange. ' +
'Should be a string, like "route" to match an event like "routeChangeStart"');
var removeListener = $rootScope.$on('$' + data.closeOnRouteChange + 'ChangeStart', function() {
notif.close();
removeListener();
});
}
// start the timer
if (~data.duration)
notif.$timeoutPromise = $timeout(notif.close.bind(notif), data.duration);
// user callback
data.show();
};
notif = {
data: data,
close: function() {
// remove timeout in case of user action
if (this.$timeoutPromise)
$timeout.cancel(this.$timeoutPromise);
// user callback
if (data.close() === false)
return;
// remove from queue
queue.splice(queue.indexOf(this), 1);
// if we closed the notif before it could be printed, prevent it trying to open later
showNotif = angular.noop;
// remove from DOM if it was inserted
if (this.templateElement)
this.templateElement.remove();
}
};
// retrieve template file
var $http = $injector.get('$http');
$http
.get(data.templatesDir + data.templateFile, {cache: $templateCache})
.success(function(template) {
showNotif(template);
})
.error(function(data) {
throw new Error('Template specified for notifications (' + data.template + ') could not be loaded. ' + data);
});
return notif;
}
function notifyByType(type) {
return function(title, message, data) {
data = data || {};
if (typeof title === 'object')
data = title;
else {
data.title = title;
data.message = message;
}
data.type = type;
// set type fa-icon
if (data.faIcons && (type in faIcons))
data.faIcon = faIcons[type];
return notify(data);
};
}
return {
queue: queue,
info: notifyByType('info'),
error: notifyByType('error'),
success: notifyByType('success'),
warning: notifyByType('warning'),
notify: notify
};
}];
});