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