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);