Mercurial > hg > dml-open-vis
diff src/DML/MainVisBundle/Resources/assets/marionette/modules/NotificationsModule.js @ 0:493bcb69166c
added public content
author | Daniel Wolff |
---|---|
date | Tue, 09 Feb 2016 20:54:02 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DML/MainVisBundle/Resources/assets/marionette/modules/NotificationsModule.js Tue Feb 09 20:54:02 2016 +0100 @@ -0,0 +1,336 @@ +"use strict"; +/** + * Notification options: + * + * id + * null or string + * if the same as before, the old notification with the same id is replaced with the current one + * + * modifiers + * [string1, string2] + * bem modifiers (define look and feel) + * availabilities: + * no-padding + * type_warning + * type_error + * ttl_ever + * ttl_10 + * ttl_11 + * ttl_20 + * ... + * + * content + * text / jquery object + * what to show in the notification + * + * onClose + * function(options) + * what to do when the notification is manually closed + * + * onExpire + * function(options) + * what to do when the notification is removed automatically + * + * keepContentInMemoryAfterRemoval + * does not destroy the content on notification removal (it retains DOM nodes and event listeners, which can be reused later) + * + */ +App.module("NotificationsModule", function(NotificationsModule, App, Backbone, Marionette, $, _, Logger) { + // Prevent auto start + NotificationsModule.startWithParent = false; + + // Define options + var defaultModuleOptions = { + defaultTTL: 10, // time to live + minTTLOnUnfreeze: 6 // time to live after the notification is unfrozen (mouse went out) + }; + var moduleOptions; + + // Define private variables + var logger = null; + var $notifications = null; + var $notificationStack = null; + + // Define private methods + var addNewNotification = null; + var removeNotification = null; + var updateNotification = null; + + var notificationTimeoutHandler = null; + + /* + * Structure of "$notification" object + * .notification_body + * .notification_content + * .notification_closer + * data + * options + * ... + */ + + /** + * Initialization checker + */ + var assertModuleIsInitialized = function() { + if (!$notifications) { + throw "NotificationsModule has not been initialized"; + } + }; + + /** + * Module initializer + * + */ + NotificationsModule.addInitializer(function(options){ + moduleOptions = _.extend(defaultModuleOptions, options); + + logger = Logger.get("NotificationsModule"); + //logger.setLevel(Logger.DEBUG); + + logger.debug("Begin init"); + + $notifications = $(".app__notifications") + .addClass("notifications"); + + $notificationStack = $.bem.generateElement("notifications", "stack") + .appendTo($notifications); + + // Sets up the internals of the notification + var setupNotification = function($notification, notificationOptions) { + // Content + var $notificationContent = $notification.children().eq(0).children().eq(0); + if (!$notificationContent.hasClass("notification__content")) { + $notificationContent = $notificationContent.children().eq(0); + } + $notificationContent.children().detach(); + if (notificationOptions.content instanceof $) { + $notificationContent.append(notificationOptions.content); + } else { + $notificationContent.html(notificationOptions.content); + } + + $notification.attr("data-id", notificationOptions.id); + $notification.data("options", notificationOptions); + $notification.data("$content", $notificationContent); + + // Update css modifiers + var modifiersAsArray = null; + if (_.isString(notificationOptions.modifiers)) { + modifiersAsArray = notificationOptions.modifiers.split(" "); + } + if (_.isArray(notificationOptions.modifiers)){ + modifiersAsArray = notificationOptions.modifiers; + } + var state = $notification.getMod("notification", "state"); + $notification.attr("class", "notification notification_state_" + state); + if (_.isArray(modifiersAsArray) && modifiersAsArray.length) { + $notification.addClass("notification_" + modifiersAsArray.join(" notification_")); + } + + // Set ttl (time to live) if needed + if (!$notification.getMod("notification", "ttl")) { + $notification.setMod("notification", "ttl", moduleOptions.defaultTTL); + } + }; + + var freezeTTL = function () { + logger.debug("Freeze ttl", this); + $(this).addClass("notification_ttl-frozen"); + }; + var unfreezeTTL = function () { + logger.debug("Unfreeze ttl", this); + var $this = $(this); + var ttl = $this.getMod("notification", "ttl"); + if (ttl * 1 == ttl && ttl < moduleOptions.minTTLOnUnfreeze) { + $this.setMod("notification", "ttl", moduleOptions.minTTLOnUnfreeze); + } + $this.removeClass("notification_ttl-frozen"); + }; + var stopTTL = function () { + logger.debug("Stop ttl", this); + $(this).setMod("notification", "ttl", "ever"); + }; + + // A function that adds a new notification + addNewNotification = function(notificationOptions) { + logger.info("Adding new notificaton", notificationOptions); + + var $notificationContent = $.bem.generateElement("notification", "content"); + var $notificationCloser = $.bem.generateElement("notification", "closer"); + var $notificationBody = $.bem.generateElement("notification", "body") + .append($notificationContent, $notificationCloser); + + var $notification = $.bem.generateBlock("notification") + .append($notificationBody); + + var closeFunction = function() { + removeNotification($notification); + var onClose = notificationOptions.onClose; + if (_.isFunction(onClose)) { + try { + onClose($notification); + } catch (e) { + logger.error("Error when handling onClose", e, $notification, onClose); + } + } + }; + $notificationCloser.click(closeFunction); + $notification.mouseover(freezeTTL); + $notification.mouseout(unfreezeTTL); + $notification.click(stopTTL); + + setupNotification($notification, notificationOptions); + $notification.setMod("notification", "state", "pre-shown"); + $notification.appendTo($notificationStack); + + var notificationBodyHeight = $notificationBody.outerHeight(); + $notification.height(notificationBodyHeight); + $notification.hide(); + $notificationBody.hide(); + + // First show the container, and then slide the body + $notification.show("blind", { + direction: "up", + //easing: "easeInOutQuart" + + }, $notificationStack.children().length == 1 ? 1 : 100 + notificationBodyHeight / 10 * 30, function() { + $notificationBody.show("slide", { + direction: "right", + easing: "easeOutQuart" + }, 500, function() { + $notification.setMod("notification", "state", "shown"); + }); + }); + }; + + // A function that replaces the contents of the given notification + updateNotification = function($notification, notificationOptions) { + logger.info("Updating notification", $notification, "with new options", notificationOptions); + + if ($notification.length !== 1) { + logger.error(_.str.sprintf("Can’t replace a notification, this can be done only when $notification contains a single instance.", $notification)); + return; + } + + // Set up the notification again + setupNotification($notification, notificationOptions); + + // Pay user's attention to this notification + if (!$notification.is(":animated") && $notification.hasClass("notification_state_shown")) { + $notification.effect("shake", { + direction: "up", + distance: 5, + times: 2 + }); + } + }; + + // Removes the notification (hides it and removes) + removeNotification = function($notification) { + logger.debug("Begin remove notification", $notification); + if ($notification.hasMod("notification", "state_pre-hidden")) { + logger.debug("Notification", $notification, "is already being hidden by another handler"); + return; + }; + $notification.setMod("notification", "state", "pre-hidden"); + + // First fade the body and then blind the container + var $notificationBody = $notification.children().eq(0); + var notificationBodyHeight = $notificationBody.outerHeight(); + $notification.children().eq(0).fadeOut( + 200, + function() { + $notification.hide("blind", { + direction: "up", + }, 100 + notificationBodyHeight / 10 * 30, function() { + if ($notification.data("options").keepContentInMemoryAfterRemoval) { + $notification.data("$content").detach(); + } + $notification.remove(); + }); + }); + + }; + + // Every second decrease ttl (time to live) for each notification + notificationTimeoutHandler = setInterval(function() { + logger.debug("Notification lifetime interval triggered"); + $notificationStack + .children() + .filter(".notification_state_shown") + .not(".notification_ttl-frozen") // this class is set to notification when it is hovered over with a mouse + .each(function() { + var $thisNotification = $(this); + var ttl = $thisNotification.getMod("notification", "ttl"); + if (ttl * 1 != ttl) { + return; + } + ttl = ttl - 1; + if (ttl < 0) { + removeNotification($thisNotification); + var onExpire = $thisNotification.data("options").onExpire; + if (_.isFunction(onExpire)) { + try { + onExpire($thisNotification); + } catch (e) { + logger.error("Error when handling onExpire", e, $thisNotification, onExpire); + } + } + + } else { + $thisNotification.setMod("notification", "ttl", ttl); + } + }); + }, 1000); + + logger.debug("End init"); + }); + + /** + * Show a notification + */ + NotificationsModule.show = function(notificationOptions) { + assertModuleIsInitialized(); + logger.debug("Begin showing a new notification", notificationOptions); + + var extendedNotificationOptions = _.extend({ + id: null, + modifiers: null, + content: "", + onClose: null, + onExpire: null, + keepContentInMemoryAfterRemoval: false + }, notificationOptions); + + // Attempt to find and update notification with the same id (if it has been defined) + // Or to create a new notification + if (extendedNotificationOptions.id) { + var $notificationWithTheSameId = $notificationStack.find(_.str.sprintf(".notification[data-id='%s']", extendedNotificationOptions.id)).not(".notification_state_pre-hidden"); + logger.debug("Detection of a notification witht the same id", extendedNotificationOptions.id, $notificationWithTheSameId, $notificationStack.children()); + if ($notificationWithTheSameId.length && ($notificationWithTheSameId.hasMod("notification", "state_shown") || $notificationWithTheSameId.hasMod("notification", "state_pre-shown"))) { + if ($notificationWithTheSameId.offset().top < 0) { + removeNotification($notificationWithTheSameId); + addNewNotification(extendedNotificationOptions); + } else { + updateNotification($notificationWithTheSameId, extendedNotificationOptions); + } + } else { + addNewNotification(extendedNotificationOptions); + } + } else { + addNewNotification(extendedNotificationOptions); + } + logger.debug("End showing a new notification", notificationOptions); + }; + + /** + * Hide a notification + */ + NotificationsModule.hide = function(notificationId) { + var $notification = $notificationStack.find(_.str.sprintf(".notification[data-id='%s']", notificationId)).not(".notification_state_pre-hidden"); + if ($notification.length) { + removeNotification($notification); + } + }; + +}, Logger);