apostrophe
Version:
The Apostrophe Content Management System.
212 lines (175 loc) • 6.6 kB
JavaScript
apos.define('apostrophe-notifications', {
extend: 'apostrophe-context',
afterConstruct: function(self) {
apos.notify = self.trigger;
apos.on('modalStackPop', self.reparentContainer);
apos.on('modalStackPush', self.reparentContainer);
self.createContainer();
self.enable();
},
construct: function(self, options) {
// Call with a message, followed by any interpolated strings which must correspond
// to %s placeholders in `message` (variable number of arguments), followed by an
// `options` object if desired.
//
// `options.type` styles the notification and may be set to `error`,
// `warn` or `success`. If not set, a "plain" default style is used.
//
// If `options.dismiss` is set to `true`, the message will auto-dismiss after 5 seconds.
// If it is set to a number of seconds, it will dismiss after that number of seconds.
// Otherwise it will not dismiss unless clicked.
//
// This method is aliased as `apos.notify` for convenience.
//
// The message is internationalized by the server, which is why the use of
// %s placeholders for any inserted titles, etc. is important.
self.trigger = function(message, options) {
var strings = [];
var i = 1;
var index = 0;
while (true) {
index = message.indexOf('%s', index);
if (index === -1) {
break;
}
// Don't match the same one over and over
index += 2;
if ((i >= arguments.length) || ((typeof (arguments[i]) === 'object'))) {
throw new Error('Bad notification call: number of %s placeholders does not match number of string arguments after message');
}
strings.push(arguments[i++]);
}
if ((i === (arguments.length - 1)) && (typeof (arguments[i]) === 'object')) {
options = arguments[i++];
} else {
options = {};
}
if (i !== arguments.length) {
throw new Error('Bad notification call: number of %s placeholders does not match number of string arguments after message');
}
if (options.dismiss === true) {
options.dismiss = 5;
}
// Send it to the server, which will send it back to us via
// the same long polling mechanism that allows it to reach
// other tabs, and allows server-sent notifications to
// reach us
self.api('trigger', {
message: message,
strings: strings,
type: options.type,
dismiss: options.dismiss,
pulse: options.pulse,
id: options.id
}, function() {
// We sent it, we're good. The server will get back to us
// and display it quite soon thanks to long polling
});
};
self.enable = function() {
// wait for apos.user to be testable
setImmediate(go);
function go() {
if (!(apos.user && apos.user._id)) {
// user styles and js are being used for extra features but
// there is no actual user, the API will fail
return;
}
self.ids = [];
poll();
function poll() {
self.api('poll-notifications', {
displayingIds: self.ids
}, function(data) {
if (data.status !== 'ok') {
apos.utils.error(data.status);
return retryLater();
}
_.each(data.notifications, function(notification) {
self.display(notification);
});
self.ids = self.ids.concat(_.pluck(data.notifications, '_id'));
self.ids = _.uniq(self.ids);
_.each(data.dismissed, function(id) {
var $notification = $('[data-apos-notification-id="' + id + '"]');
self.dismiss($notification, true);
});
// Long polling loop continues
poll();
}, function(err) {
apos.utils.error(err);
return retryLater();
});
}
function retryLater() {
setTimeout(poll, 5000);
}
}
};
// Display a notification received from the server.
// You want `apos.notify`, not this method, unless you are
// overriding how notifications are displayed.
self.display = function(notification) {
var $notification = $($.parseHTML(notification.html));
if (notification.type) {
$notification.addClass('apos-notification--' + notification.type);
}
if (notification.pulse) {
$notification.addClass('apos-notification--pulse');
}
// This is an id assigned programmatically by the developer, it
// is separate from the `_id` property assigned by the server.
if (notification.id) {
$notification.attr('id', notification.id);
}
$notification.attr('data-apos-notification-id', notification._id);
if (notification.dismiss) {
$notification.attr('data-apos-notification-dismiss', notification.dismiss);
setTimeout(function() {
self.dismiss($notification);
}, (1000 * notification.dismiss));
}
$notification.click(function() {
self.dismiss($notification);
});
self.addToContainer($notification);
};
self.createContainer = function() {
self.$container = $('<div class="apos-notification-container" data-apos-notification-container role="alert"></div>');
self.$container.hide();
$('body').append(self.$container);
};
self.reparentContainer = function() {
var $topModal = apos.modalSupport.getTopModalOrBody();
$topModal.append(self.$container);
};
self.addToContainer = function($notification) {
self.$container.prepend($notification);
self.$container.show();
setTimeout(function() {
$notification.removeClass('apos-notification--hidden');
}, 100);
};
self.dismiss = function($notification, fromServer) {
var _id = $notification.attr('data-apos-notification-id');
self.ids = _.filter(self.ids, function(id) {
return id !== _id;
});
if (!fromServer) {
// We're doing it, so tell the server
self.api('dismiss', {
_id: _id
}, function() {
// Now the server knows not to send it anymore
});
}
$notification.addClass('apos-notification--hidden');
setTimeout(function() {
$notification.remove();
if (!self.$container.find('[data-apos-notification]').length) {
self.$container.hide();
}
}, 300);
};
}
});