comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:493bcb69166c
1 "use strict";
2 /**
3 * Notification options:
4 *
5 * id
6 * null or string
7 * if the same as before, the old notification with the same id is replaced with the current one
8 *
9 * modifiers
10 * [string1, string2]
11 * bem modifiers (define look and feel)
12 * availabilities:
13 * no-padding
14 * type_warning
15 * type_error
16 * ttl_ever
17 * ttl_10
18 * ttl_11
19 * ttl_20
20 * ...
21 *
22 * content
23 * text / jquery object
24 * what to show in the notification
25 *
26 * onClose
27 * function(options)
28 * what to do when the notification is manually closed
29 *
30 * onExpire
31 * function(options)
32 * what to do when the notification is removed automatically
33 *
34 * keepContentInMemoryAfterRemoval
35 * does not destroy the content on notification removal (it retains DOM nodes and event listeners, which can be reused later)
36 *
37 */
38 App.module("NotificationsModule", function(NotificationsModule, App, Backbone, Marionette, $, _, Logger) {
39 // Prevent auto start
40 NotificationsModule.startWithParent = false;
41
42 // Define options
43 var defaultModuleOptions = {
44 defaultTTL: 10, // time to live
45 minTTLOnUnfreeze: 6 // time to live after the notification is unfrozen (mouse went out)
46 };
47 var moduleOptions;
48
49 // Define private variables
50 var logger = null;
51 var $notifications = null;
52 var $notificationStack = null;
53
54 // Define private methods
55 var addNewNotification = null;
56 var removeNotification = null;
57 var updateNotification = null;
58
59 var notificationTimeoutHandler = null;
60
61 /*
62 * Structure of "$notification" object
63 * .notification_body
64 * .notification_content
65 * .notification_closer
66 * data
67 * options
68 * ...
69 */
70
71 /**
72 * Initialization checker
73 */
74 var assertModuleIsInitialized = function() {
75 if (!$notifications) {
76 throw "NotificationsModule has not been initialized";
77 }
78 };
79
80 /**
81 * Module initializer
82 *
83 */
84 NotificationsModule.addInitializer(function(options){
85 moduleOptions = _.extend(defaultModuleOptions, options);
86
87 logger = Logger.get("NotificationsModule");
88 //logger.setLevel(Logger.DEBUG);
89
90 logger.debug("Begin init");
91
92 $notifications = $(".app__notifications")
93 .addClass("notifications");
94
95 $notificationStack = $.bem.generateElement("notifications", "stack")
96 .appendTo($notifications);
97
98 // Sets up the internals of the notification
99 var setupNotification = function($notification, notificationOptions) {
100 // Content
101 var $notificationContent = $notification.children().eq(0).children().eq(0);
102 if (!$notificationContent.hasClass("notification__content")) {
103 $notificationContent = $notificationContent.children().eq(0);
104 }
105 $notificationContent.children().detach();
106 if (notificationOptions.content instanceof $) {
107 $notificationContent.append(notificationOptions.content);
108 } else {
109 $notificationContent.html(notificationOptions.content);
110 }
111
112 $notification.attr("data-id", notificationOptions.id);
113 $notification.data("options", notificationOptions);
114 $notification.data("$content", $notificationContent);
115
116 // Update css modifiers
117 var modifiersAsArray = null;
118 if (_.isString(notificationOptions.modifiers)) {
119 modifiersAsArray = notificationOptions.modifiers.split(" ");
120 }
121 if (_.isArray(notificationOptions.modifiers)){
122 modifiersAsArray = notificationOptions.modifiers;
123 }
124 var state = $notification.getMod("notification", "state");
125 $notification.attr("class", "notification notification_state_" + state);
126 if (_.isArray(modifiersAsArray) && modifiersAsArray.length) {
127 $notification.addClass("notification_" + modifiersAsArray.join(" notification_"));
128 }
129
130 // Set ttl (time to live) if needed
131 if (!$notification.getMod("notification", "ttl")) {
132 $notification.setMod("notification", "ttl", moduleOptions.defaultTTL);
133 }
134 };
135
136 var freezeTTL = function () {
137 logger.debug("Freeze ttl", this);
138 $(this).addClass("notification_ttl-frozen");
139 };
140 var unfreezeTTL = function () {
141 logger.debug("Unfreeze ttl", this);
142 var $this = $(this);
143 var ttl = $this.getMod("notification", "ttl");
144 if (ttl * 1 == ttl && ttl < moduleOptions.minTTLOnUnfreeze) {
145 $this.setMod("notification", "ttl", moduleOptions.minTTLOnUnfreeze);
146 }
147 $this.removeClass("notification_ttl-frozen");
148 };
149 var stopTTL = function () {
150 logger.debug("Stop ttl", this);
151 $(this).setMod("notification", "ttl", "ever");
152 };
153
154 // A function that adds a new notification
155 addNewNotification = function(notificationOptions) {
156 logger.info("Adding new notificaton", notificationOptions);
157
158 var $notificationContent = $.bem.generateElement("notification", "content");
159 var $notificationCloser = $.bem.generateElement("notification", "closer");
160 var $notificationBody = $.bem.generateElement("notification", "body")
161 .append($notificationContent, $notificationCloser);
162
163 var $notification = $.bem.generateBlock("notification")
164 .append($notificationBody);
165
166 var closeFunction = function() {
167 removeNotification($notification);
168 var onClose = notificationOptions.onClose;
169 if (_.isFunction(onClose)) {
170 try {
171 onClose($notification);
172 } catch (e) {
173 logger.error("Error when handling onClose", e, $notification, onClose);
174 }
175 }
176 };
177 $notificationCloser.click(closeFunction);
178 $notification.mouseover(freezeTTL);
179 $notification.mouseout(unfreezeTTL);
180 $notification.click(stopTTL);
181
182 setupNotification($notification, notificationOptions);
183 $notification.setMod("notification", "state", "pre-shown");
184 $notification.appendTo($notificationStack);
185
186 var notificationBodyHeight = $notificationBody.outerHeight();
187 $notification.height(notificationBodyHeight);
188 $notification.hide();
189 $notificationBody.hide();
190
191 // First show the container, and then slide the body
192 $notification.show("blind", {
193 direction: "up",
194 //easing: "easeInOutQuart"
195
196 }, $notificationStack.children().length == 1 ? 1 : 100 + notificationBodyHeight / 10 * 30, function() {
197 $notificationBody.show("slide", {
198 direction: "right",
199 easing: "easeOutQuart"
200 }, 500, function() {
201 $notification.setMod("notification", "state", "shown");
202 });
203 });
204 };
205
206 // A function that replaces the contents of the given notification
207 updateNotification = function($notification, notificationOptions) {
208 logger.info("Updating notification", $notification, "with new options", notificationOptions);
209
210 if ($notification.length !== 1) {
211 logger.error(_.str.sprintf("Can’t replace a notification, this can be done only when $notification contains a single instance.", $notification));
212 return;
213 }
214
215 // Set up the notification again
216 setupNotification($notification, notificationOptions);
217
218 // Pay user's attention to this notification
219 if (!$notification.is(":animated") && $notification.hasClass("notification_state_shown")) {
220 $notification.effect("shake", {
221 direction: "up",
222 distance: 5,
223 times: 2
224 });
225 }
226 };
227
228 // Removes the notification (hides it and removes)
229 removeNotification = function($notification) {
230 logger.debug("Begin remove notification", $notification);
231 if ($notification.hasMod("notification", "state_pre-hidden")) {
232 logger.debug("Notification", $notification, "is already being hidden by another handler");
233 return;
234 };
235 $notification.setMod("notification", "state", "pre-hidden");
236
237 // First fade the body and then blind the container
238 var $notificationBody = $notification.children().eq(0);
239 var notificationBodyHeight = $notificationBody.outerHeight();
240 $notification.children().eq(0).fadeOut(
241 200,
242 function() {
243 $notification.hide("blind", {
244 direction: "up",
245 }, 100 + notificationBodyHeight / 10 * 30, function() {
246 if ($notification.data("options").keepContentInMemoryAfterRemoval) {
247 $notification.data("$content").detach();
248 }
249 $notification.remove();
250 });
251 });
252
253 };
254
255 // Every second decrease ttl (time to live) for each notification
256 notificationTimeoutHandler = setInterval(function() {
257 logger.debug("Notification lifetime interval triggered");
258 $notificationStack
259 .children()
260 .filter(".notification_state_shown")
261 .not(".notification_ttl-frozen") // this class is set to notification when it is hovered over with a mouse
262 .each(function() {
263 var $thisNotification = $(this);
264 var ttl = $thisNotification.getMod("notification", "ttl");
265 if (ttl * 1 != ttl) {
266 return;
267 }
268 ttl = ttl - 1;
269 if (ttl < 0) {
270 removeNotification($thisNotification);
271 var onExpire = $thisNotification.data("options").onExpire;
272 if (_.isFunction(onExpire)) {
273 try {
274 onExpire($thisNotification);
275 } catch (e) {
276 logger.error("Error when handling onExpire", e, $thisNotification, onExpire);
277 }
278 }
279
280 } else {
281 $thisNotification.setMod("notification", "ttl", ttl);
282 }
283 });
284 }, 1000);
285
286 logger.debug("End init");
287 });
288
289 /**
290 * Show a notification
291 */
292 NotificationsModule.show = function(notificationOptions) {
293 assertModuleIsInitialized();
294 logger.debug("Begin showing a new notification", notificationOptions);
295
296 var extendedNotificationOptions = _.extend({
297 id: null,
298 modifiers: null,
299 content: "",
300 onClose: null,
301 onExpire: null,
302 keepContentInMemoryAfterRemoval: false
303 }, notificationOptions);
304
305 // Attempt to find and update notification with the same id (if it has been defined)
306 // Or to create a new notification
307 if (extendedNotificationOptions.id) {
308 var $notificationWithTheSameId = $notificationStack.find(_.str.sprintf(".notification[data-id='%s']", extendedNotificationOptions.id)).not(".notification_state_pre-hidden");
309 logger.debug("Detection of a notification witht the same id", extendedNotificationOptions.id, $notificationWithTheSameId, $notificationStack.children());
310 if ($notificationWithTheSameId.length && ($notificationWithTheSameId.hasMod("notification", "state_shown") || $notificationWithTheSameId.hasMod("notification", "state_pre-shown"))) {
311 if ($notificationWithTheSameId.offset().top < 0) {
312 removeNotification($notificationWithTheSameId);
313 addNewNotification(extendedNotificationOptions);
314 } else {
315 updateNotification($notificationWithTheSameId, extendedNotificationOptions);
316 }
317 } else {
318 addNewNotification(extendedNotificationOptions);
319 }
320 } else {
321 addNewNotification(extendedNotificationOptions);
322 }
323 logger.debug("End showing a new notification", notificationOptions);
324 };
325
326 /**
327 * Hide a notification
328 */
329 NotificationsModule.hide = function(notificationId) {
330 var $notification = $notificationStack.find(_.str.sprintf(".notification[data-id='%s']", notificationId)).not(".notification_state_pre-hidden");
331 if ($notification.length) {
332 removeNotification($notification);
333 }
334 };
335
336 }, Logger);