MDL-55947 core_message: fixed order of notifications in popover
[moodle.git] / message / amd / src / notification_popover_controller.js
CommitLineData
a0e358a6
RW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Controls the notification popover in the nav bar.
18 *
19 * See template: message/notification_menu
20 *
9e8a29c9 21 * @module core_message/notification_popover_controller
a0e358a6
RW
22 * @class notification_popover_controller
23 * @package message
24 * @copyright 2016 Ryan Wyllie <ryan@moodle.com>
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 * @since 3.2
27 */
28define(['jquery', 'theme_bootstrapbase/bootstrap', 'core/ajax', 'core/templates', 'core/str',
d4555a3d 29 'core/notification', 'core/custom_interaction_events', 'core/popover_region_controller',
9e8a29c9 30 'core_message/notification_repository'],
34eb5fcb 31 function($, Bootstrap, Ajax, Templates, Str, DebugNotification, CustomEvents,
195a683b 32 PopoverController, NotificationRepo) {
a0e358a6
RW
33
34 var SELECTORS = {
6af2bd09
RW
35 MARK_ALL_READ_BUTTON: '[data-action="mark-all-read"]',
36 MODE_TOGGLE: '[data-region="popover-region-header-actions"] [data-region="fancy-toggle"]',
37 UNREAD_NOTIFICATIONS_CONTAINER: '[data-region="unread-notifications"]',
38 ALL_NOTIFICATIONS_CONTAINER: '[data-region="all-notifications"]',
39 BLOCK_BUTTON: '[data-action="block-button"]',
40 SHOW_BUTTON: '[data-action="show-button"]',
41 HIDE_BUTTON: '[data-action="hide-button"]',
42 CONTENT_ITEM_CONTAINER: '[data-region="notification-content-item-container"]',
43 EMPTY_MESSAGE: '[data-region="empty-message"]',
44 CONTENT_BODY_SHORT: '[data-region="content-body-short"]',
45 CONTENT_BODY_FULL: '[data-region="content-body-full"]',
c826fa23 46 LINK_URL: '[data-link-url]',
6af2bd09 47 COUNT_CONTAINER: '[data-region="count-container"]',
a0e358a6
RW
48 };
49
c5dd16a1
RW
50 var PROCESSOR_NAME = 'popup';
51
a0e358a6
RW
52 /**
53 * Constructor for the NotificationPopoverController.
d4555a3d 54 * Extends PopoverRegionController.
a0e358a6
RW
55 *
56 * @param element jQuery object root element of the popover
57 * @return object NotificationPopoverController
58 */
59 var NotificationPopoverController = function(element) {
60 // Initialise base class.
195a683b 61 PopoverController.call(this, element);
a0e358a6
RW
62
63 this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
64 this.unreadCount = 0;
6af2bd09 65 this.userId = this.root.attr('data-userid');
a0e358a6
RW
66 this.modeToggle = this.root.find(SELECTORS.MODE_TOGGLE);
67 this.state = {
68 unread: {
69 container: this.root.find(SELECTORS.UNREAD_NOTIFICATIONS_CONTAINER),
70 limit: 6,
71 offset: 0,
72 loadedAll: false,
73 initialLoad: false,
74 },
75 all: {
76 container: this.root.find(SELECTORS.ALL_NOTIFICATIONS_CONTAINER),
77 limit: 20,
78 offset: 0,
79 loadedAll: false,
80 initialLoad: false,
81 }
82 };
83
84 // Let's find out how many unread notifications there are.
85 this.loadUnreadNotificationCount();
86 this.root.find('[data-toggle="tooltip"]').tooltip();
87 };
88
89 /**
90 * Clone the parent prototype.
91 */
195a683b 92 NotificationPopoverController.prototype = Object.create(PopoverController.prototype);
a0e358a6 93
34eb5fcb
RW
94 /**
95 * Make sure the constructor is set correctly.
96 */
97 NotificationPopoverController.prototype.constructor = NotificationPopoverController;
98
a0e358a6
RW
99 /**
100 * Set the correct aria label on the menu toggle button to be read out by screen
101 * readers. The message will indicate the state of the unread notifications.
102 *
103 * @method updateButtonAriaLabel
104 */
105 NotificationPopoverController.prototype.updateButtonAriaLabel = function() {
106 if (this.isMenuOpen()) {
34eb5fcb 107 Str.get_string('hidenotificationwindow', 'message').done(function(string) {
a0e358a6
RW
108 this.menuToggle.attr('aria-label', string);
109 }.bind(this));
110 } else {
111 if (this.unreadCount) {
34eb5fcb 112 Str.get_string('shownotificationwindowwithcount', 'message', this.unreadCount).done(function(string) {
a0e358a6
RW
113 this.menuToggle.attr('aria-label', string);
114 }.bind(this));
115 } else {
34eb5fcb 116 Str.get_string('shownotificationwindownonew', 'message').done(function(string) {
a0e358a6
RW
117 this.menuToggle.attr('aria-label', string);
118 }.bind(this));
119 }
120 }
121 };
122
123 /**
124 * Return the jQuery element with the content. This will return either
125 * the unread notification container or the all notification container
126 * depending on which is currently visible.
127 *
128 * @method getContent
129 * @return jQuery object currently visible content contianer
130 */
131 NotificationPopoverController.prototype.getContent = function() {
132 return this.getState().container;
133 };
134
135 /**
136 * Check whether the notification menu is showing unread notification or
137 * all notifications.
138 *
139 * @method unreadOnlyMode
140 * @return bool true if only showing unread notifications, false otherwise
141 */
142 NotificationPopoverController.prototype.unreadOnlyMode = function() {
143 return this.modeToggle.hasClass('on');
144 };
145
146 /**
147 * Get the current state of the notification menu. Checks whether
148 * the popover is in unread only mode.
149 *
150 * The internal state tracks various properties required for loading
151 * notifications.
152 *
153 * @method getState
154 * @return object unread state or all state
155 */
156 NotificationPopoverController.prototype.getState = function() {
157 if (this.unreadOnlyMode()) {
158 return this.state.unread;
159 } else {
160 return this.state.all;
161 }
162 };
163
164 /**
165 * Get the offset value for the current state of the popover in order
166 * to sent to the backend to correctly paginate the notifications.
167 *
168 * @method getOffset
169 * @return int current offset
170 */
171 NotificationPopoverController.prototype.getOffset = function() {
172 return this.getState().offset;
173 };
174
175 /**
176 * Increment the offset for the current state, if required.
177 *
178 * @method incrementOffset
179 */
180 NotificationPopoverController.prototype.incrementOffset = function() {
181 // Only need to increment offset if we're combining read and unread
182 // because all unread messages are marked as read when we retrieve them
183 // which acts as the result set increment for us.
184 if (!this.unreadOnlyMode()) {
185 this.getState().offset += this.getState().limit;
186 }
187 };
188
189 /**
190 * Reset the offset to zero for the current state.
191 *
192 * @method resetOffset
193 */
194 NotificationPopoverController.prototype.resetOffset = function() {
195 this.getState().offset = 0;
196 };
197
198 /**
199 * Check if the first load of notification has been triggered for the current
200 * state of the popover.
201 *
202 * @method hasDoneInitialLoad
203 * @return bool true if first notification loaded, false otherwise
204 */
205 NotificationPopoverController.prototype.hasDoneInitialLoad = function() {
206 return this.getState().initialLoad;
207 };
208
209 /**
210 * Check if we've loaded all of the notifications for the current popover
211 * state.
212 *
213 * @method hasLoadedAllContent
214 * @return bool true if all notifications loaded, false otherwise
215 */
216 NotificationPopoverController.prototype.hasLoadedAllContent = function() {
217 return this.getState().loadedAll;
218 };
219
220 /**
221 * Set the state of the loaded all content property for the current state
222 * of the popover.
223 *
224 * @method setLoadedAllContent
225 * @param bool true if all content is loaded, false otherwise
226 */
227 NotificationPopoverController.prototype.setLoadedAllContent = function(val) {
228 this.getState().loadedAll = val;
229 };
230
231 /**
232 * Reset the unread notification state and empty the unread notification content
233 * element.
234 *
235 * @method clearUnreadNotifications
236 */
237 NotificationPopoverController.prototype.clearUnreadNotifications = function() {
238 this.state.unread.offset = 0;
239 this.state.unread.loadedAll = false;
240 this.state.unread.initialLoad = false;
241 this.state.unread.container.empty();
242 };
243
244 /**
245 * Show the unread notification count badge on the menu toggle if there
246 * are unread notifications, otherwise hide it.
247 *
248 * @method renderUnreadCount
249 */
250 NotificationPopoverController.prototype.renderUnreadCount = function() {
6af2bd09 251 var element = this.root.find(SELECTORS.COUNT_CONTAINER);
a0e358a6
RW
252
253 if (this.unreadCount) {
254 element.text(this.unreadCount);
255 element.removeClass('hidden');
256 } else {
257 element.addClass('hidden');
258 }
259 };
260
261 /**
262 * Hide the unread notification count badge on the menu toggle.
263 *
264 * @method hideUnreadCount
265 */
266 NotificationPopoverController.prototype.hideUnreadCount = function() {
6af2bd09 267 this.root.find(SELECTORS.COUNT_CONTAINER).addClass('hidden');
a0e358a6
RW
268 };
269
270 /**
271 * Ask the server how many unread notifications are left, render the value
272 * as a badge on the menu toggle and update the aria labels on the menu
273 * toggle.
274 *
275 * @method loadUnreadNotificationCount
276 */
277 NotificationPopoverController.prototype.loadUnreadNotificationCount = function() {
195a683b 278 NotificationRepo.countUnread({useridto: this.userId}).then(function(count) {
a0e358a6
RW
279 this.unreadCount = count;
280 this.renderUnreadCount();
281 this.updateButtonAriaLabel();
282 }.bind(this));
283 };
284
285 /**
286 * Render the notification data with the appropriate template and add it to the DOM.
287 *
288 * @method renderNotifications
289 * @param notifications array notification data
290 * @param container jQuery object the container to append the rendered notifications
291 * @return jQuery promise that is resolved when all notifications have been
292 * rendered and added to the DOM
293 */
294 NotificationPopoverController.prototype.renderNotifications = function(notifications, container) {
295 var promises = [];
30aac24d
MN
296 var allhtml = [];
297 var alljs = [];
a0e358a6
RW
298
299 if (notifications.length) {
300 $.each(notifications, function(index, notification) {
c5dd16a1
RW
301 notification.preferenceenabled = false;
302
303 // Check if we should display the preference block button.
304 if (notification.preference) {
305 var regexp = new RegExp(PROCESSOR_NAME);
306 if (notification.preference.loggedin.match(regexp) || notification.preference.loggedoff.match(regexp)) {
307 notification.preferenceenabled = true;
308 }
309 }
310
34eb5fcb 311 var promise = Templates.render('message/notification_content_item', notification);
30aac24d
MN
312 promises.push(promise);
313
a0e358a6 314 promise.then(function(html, js) {
30aac24d
MN
315 allhtml[index] = html;
316 alljs[index] = js;
a0e358a6 317 }.bind(this));
a0e358a6
RW
318 }.bind(this));
319 }
320
30aac24d
MN
321 return $.when.apply($.when, promises).then(function() {
322 if (notifications.length) {
323 $.each(notifications, function(index) {
324 container.append(allhtml[index]);
325 Templates.runTemplateJS(alljs[index]);
326 });
327 }
328 });
a0e358a6
RW
329 };
330
331 /**
332 * Send a request for more notifications from the server, if we aren't already
333 * loading some and haven't already loaded all of them.
334 *
335 * Takes into account the current mode of the popover and will request only
336 * unread notifications if required.
337 *
338 * All notifications are marked as read by the server when they are returned.
339 *
340 * @method loadMoreNotifications
341 * @return jQuery promise that is resolved when notifications have been
342 * retrieved and added to the DOM
343 */
344 NotificationPopoverController.prototype.loadMoreNotifications = function() {
345 if (this.isLoading || this.hasLoadedAllContent()) {
346 return $.Deferred().resolve();
347 }
348
349 this.startLoading();
350 var request = {
351 limit: this.limit,
352 offset: this.getOffset(),
353 useridto: this.userId,
354 markasread: true,
c5dd16a1 355 embedpreference: true,
a0e358a6
RW
356 embeduserto: false,
357 embeduserfrom: true,
358 };
359
360 if (this.unreadOnlyMode()) {
361 request.status = 'unread';
362 }
363
364 var container = this.getContent();
195a683b 365 var promise = NotificationRepo.query(request).then(function(result) {
a0e358a6
RW
366 var notifications = result.notifications;
367 this.unreadCount = result.unreadcount;
368 this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
369 this.getState().initialLoad = true;
370 this.updateButtonAriaLabel();
371
372 if (notifications.length) {
373 this.incrementOffset();
374 return this.renderNotifications(notifications, container);
375 }
376 }.bind(this))
377 .always(function() { this.stopLoading(); }.bind(this));
378
379 return promise;
380 };
381
382 /**
383 * Send a request to the server to mark all unread notifications as read and update
384 * the unread count and unread notification elements appropriately.
385 *
386 * @method markAllAsRead
387 */
388 NotificationPopoverController.prototype.markAllAsRead = function() {
389 this.markAllReadButton.addClass('loading');
390
195a683b 391 return NotificationRepo.markAllAsRead({useridto: this.userId})
a0e358a6
RW
392 .then(function() {
393 this.unreadCount = 0;
394 this.clearUnreadNotifications();
395 }.bind(this))
396 .always(function() { this.markAllReadButton.removeClass('loading'); }.bind(this));
397 };
398
399 /**
400 * Shift focus to the next content item in the list if the content item
401 * list current contains focus, otherwise the first item in the list is
402 * given focus.
403 *
d4555a3d 404 * Overrides PopoverRegionController.focusNextContentItem
a0e358a6
RW
405 * @method focusNextContentItem
406 */
407 NotificationPopoverController.prototype.focusNextContentItem = function() {
408 var currentFocus = $(document.activeElement);
409 var container = this.getContent();
410
411 if (container.has(currentFocus).length) {
412 var currentNotification = currentFocus.closest(SELECTORS.CONTENT_ITEM_CONTAINER);
413 currentNotification.next().focus();
414 } else {
415 this.focusFirstContentItem();
416 }
417 };
418
419 /**
420 * Shift focus to the previous content item in the content item list, if the
421 * content item list contains focus.
422 *
d4555a3d 423 * Overrides PopoverRegionController.focusPreviousContentItem
a0e358a6
RW
424 * @method focusPreviousContentItem
425 */
426 NotificationPopoverController.prototype.focusPreviousContentItem = function() {
427 var currentFocus = $(document.activeElement);
428 var container = this.getContent();
429
430 if (container.has(currentFocus).length) {
431 var currentNotification = currentFocus.closest(SELECTORS.CONTENT_ITEM_CONTAINER);
432 currentNotification.prev().focus();
433 }
434 };
435
436 /**
437 * Give focus to the first item in the list of content items.
438 *
d4555a3d 439 * Overrides PopoverRegionController.focusFirstContentItem
a0e358a6
RW
440 * @method focusFirstContentItem
441 */
442 NotificationPopoverController.prototype.focusFirstContentItem = function() {
443 var container = this.getContent();
444 var notification = container.children().first();
445
446 if (!notification.length) {
447 // If we don't have any notifications then we should focus the empty
448 // empty message for the user.
449 notification = container.next(SELECTORS.EMPTY_MESSAGE);
450 }
451
452 notification.focus();
453 };
454
455 /**
456 * Give focus to the last item in the list of content items, that is the list
457 * of notifications that have already been loaded.
458 *
d4555a3d 459 * Overrides PopoverRegionController.focusLastContentItem
a0e358a6
RW
460 * @method focusLastContentItem
461 */
462 NotificationPopoverController.prototype.focusLastContentItem = function() {
463 var container = this.getContent();
464 var notification = container.children().last();
465
466 if (!notification.length) {
467 // If we don't have any notifications then we should focus the empty
468 // empty message for the user.
469 notification = container.next(SELECTORS.EMPTY_MESSAGE);
470 }
471
472 notification.focus();
473 };
474
475 /**
476 * Expand all the currently rendered notificaitons in the current state
477 * of the popover (unread or all).
478 *
479 * @method expandAllContentItems
480 */
481 NotificationPopoverController.prototype.expandAllContentItems = function() {
482 this.getContent()
483 .find(SELECTORS.CONTENT_ITEM_CONTAINER)
484 .addClass('expanded')
485 .attr('aria-expanded', 'true');
486 };
487
488 /**
489 * Expand a single content item.
490 *
491 * @method expandContentItem
492 * @param item jQuery object the content item to be expanded
493 */
494 NotificationPopoverController.prototype.expandContentItem = function(item) {
495 item.addClass('expanded');
496 item.attr('aria-expanded', 'true');
497 item.find(SELECTORS.SHOW_BUTTON).attr('aria-hidden', 'true');
498 item.find(SELECTORS.CONTENT_BODY_SHORT).attr('aria-hidden', 'true');
499 item.find(SELECTORS.CONTENT_BODY_FULL).attr('aria-hidden', 'false');
500 item.find(SELECTORS.HIDE_BUTTON).attr('aria-hidden', 'false').focus();
501 };
502
503 /**
504 * Collapse a single content item.
505 *
506 * @method collapseContentItem
507 * @param item jQuery object the content item to be collapsed.
508 */
509 NotificationPopoverController.prototype.collapseContentItem = function(item) {
510 item.removeClass('expanded');
511 item.attr('aria-expanded', 'false');
512 item.find(SELECTORS.HIDE_BUTTON).attr('aria-hidden', 'true');
513 item.find(SELECTORS.CONTENT_BODY_FULL).attr('aria-hidden', 'true');
514 item.find(SELECTORS.CONTENT_BODY_SHORT).attr('aria-hidden', 'false');
515 item.find(SELECTORS.SHOW_BUTTON).attr('aria-hidden', 'false').focus();
516 };
517
c5dd16a1
RW
518 /**
519 * Remove the notification buttons for the given type of notification.
520 *
521 * @method removeDisableNotificationButtons
522 * @param type the type of notification to remove the button from
523 */
524 NotificationPopoverController.prototype.removeDisableNotificationButtons = function(type) {
525 this.root.find('[data-preference-key="'+type+'"]').remove();
526 };
527
528 /**
529 * Stop future notifications of this type appearing in the popover menu.
530 *
531 * @method disableNotificationType
532 * @param button jQuery object
533 */
534 NotificationPopoverController.prototype.disableNotificationType = function(button) {
535 if (button.hasClass('loading')) {
536 return $.Deferred();
537 }
538
539 button.addClass('loading');
540
541 var key = button.attr('data-preference-key');
542 var loggedin = button.attr('data-preference-loggedin');
543 var loggedoff = button.attr('data-preference-loggedoff');
544
545 // Remove the popup processor from the list.
546 loggedin = loggedin.split(',').filter(function(element) {
547 return element !== PROCESSOR_NAME;
548 }).join(',');
549
550 // Remove the popup processor from the list.
551 loggedoff = loggedoff.split(',').filter(function(element) {
552 return element !== PROCESSOR_NAME;
553 }).join(',');
554
555 // If no other processors are left then default to none.
556 if (loggedin === '') {
557 loggedin = 'none';
558 }
559
560 // If no other processors are left then default to none.
561 if (loggedoff === '') {
562 loggedoff = 'none';
563 }
564
565 var args = {
566 user: {
567 preferences: [
568 {
569 type: key + '_loggedin',
570 value: loggedin
571 },
572 {
573 type: key + '_loggedoff',
574 value: loggedoff
575 }
576 ]
577 }
578 };
579
580 var request = {
581 methodname: 'core_user_update_user',
582 args: args
583 };
584
34eb5fcb 585 var promise = Ajax.call([request])[0];
c5dd16a1 586
195a683b 587 promise.fail(DebugNotification.exception);
c5dd16a1
RW
588 promise.always(function() {
589 button.removeClass('loading');
590 });
591 promise.done(function() {
592 this.removeDisableNotificationButtons(key);
593 }.bind(this));
594
595 return promise;
596 };
597
a0e358a6
RW
598 /**
599 * Add all of the required event listeners for this notification popover.
600 *
601 * @method registerEventListeners
602 */
603 NotificationPopoverController.prototype.registerEventListeners = function() {
195a683b
RW
604 CustomEvents.define(this.root, [
605 CustomEvents.events.activate,
606 CustomEvents.events.keyboardActivate,
607 CustomEvents.events.next,
608 CustomEvents.events.previous,
609 CustomEvents.events.asterix,
a0e358a6
RW
610 ]);
611
612 // Expand the content item if the user activates (click/enter/space) the show
613 // button.
195a683b 614 this.root.on(CustomEvents.events.activate, SELECTORS.SHOW_BUTTON, function(e, data) {
a0e358a6
RW
615 var container = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
616 this.expandContentItem(container);
617
618 e.stopPropagation();
619 data.originalEvent.preventDefault();
620 }.bind(this));
621
622 // Expand the content item if the user triggers the next event (right arrow in LTR).
195a683b 623 this.root.on(CustomEvents.events.next, SELECTORS.CONTENT_ITEM_CONTAINER, function(e) {
a0e358a6
RW
624 var contentItem = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
625 this.expandContentItem(contentItem);
626 }.bind(this));
627
628 // Collapse the content item if the user activates the hide button.
195a683b 629 this.root.on(CustomEvents.events.activate, SELECTORS.HIDE_BUTTON, function(e, data) {
a0e358a6
RW
630 var container = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
631 this.collapseContentItem(container);
632
633 e.stopPropagation();
634 data.originalEvent.preventDefault();
635 }.bind(this));
636
637 // Collapse the content item if the user triggers the previous event (left arrow in LTR).
195a683b 638 this.root.on(CustomEvents.events.previous, SELECTORS.CONTENT_ITEM_CONTAINER, function(e) {
a0e358a6
RW
639 var contentItem = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
640 this.collapseContentItem(contentItem);
641 }.bind(this));
642
195a683b 643 this.root.on(CustomEvents.events.activate, SELECTORS.BLOCK_BUTTON, function(e, data) {
c5dd16a1
RW
644 var button = $(e.target).closest(SELECTORS.BLOCK_BUTTON);
645 this.disableNotificationType(button);
646
647 e.stopPropagation();
648 data.originalEvent.preventDefault();
649 }.bind(this));
650
a0e358a6 651 // Switch between popover states (read/unread) if the user activates the toggle.
195a683b 652 this.root.on(CustomEvents.events.activate, SELECTORS.MODE_TOGGLE, function(e) {
a0e358a6
RW
653 if (this.modeToggle.hasClass('on')) {
654 this.clearUnreadNotifications();
655 this.modeToggle.removeClass('on');
656 this.modeToggle.addClass('off');
657 this.root.removeClass('unread-only');
658
34eb5fcb 659 Str.get_string('shownewnotifications', 'message').done(function(string) {
a0e358a6
RW
660 this.modeToggle.attr('aria-label', string);
661 }.bind(this));
662 } else {
663 this.modeToggle.removeClass('off');
664 this.modeToggle.addClass('on');
665 this.root.addClass('unread-only');
666
34eb5fcb 667 Str.get_string('showallnotifications', 'message').done(function(string) {
a0e358a6
RW
668 this.modeToggle.attr('aria-label', string);
669 }.bind(this));
670 }
671
672 if (!this.hasDoneInitialLoad()) {
673 this.loadMoreNotifications();
674 }
675
676 e.stopPropagation();
677 }.bind(this));
678
a0e358a6 679 // Mark all notifications read if the user activates the mark all as read button.
195a683b 680 this.root.on(CustomEvents.events.activate, SELECTORS.MARK_ALL_READ_BUTTON, function(e) {
a0e358a6
RW
681 this.markAllAsRead();
682 e.stopPropagation();
683 }.bind(this));
684
685 // Expand all the currently visible content items if the user hits the
686 // asterix key.
195a683b 687 this.root.on(CustomEvents.events.asterix, function() {
a0e358a6
RW
688 this.expandAllContentItems();
689 }.bind(this));
690
691 // Update the notification information when the menu is opened.
692 this.root.on(this.events().menuOpened, function() {
693 this.hideUnreadCount();
694 this.updateButtonAriaLabel();
695
696 if (!this.hasDoneInitialLoad()) {
697 this.loadMoreNotifications();
698 }
699 }.bind(this));
700
701 // Update the unread notification count when the menu is closed.
702 this.root.on(this.events().menuClosed, function() {
703 this.renderUnreadCount();
704 this.clearUnreadNotifications();
705 this.updateButtonAriaLabel();
706 }.bind(this));
707
708 // Set aria attributes when popover is loading.
709 this.root.on(this.events().startLoading, function() {
710 this.getContent().attr('aria-busy', 'true');
711 }.bind(this));
712
713 // Set aria attributes when popover is finished loading.
714 this.root.on(this.events().stopLoading, function() {
715 this.getContent().attr('aria-busy', 'false');
716 }.bind(this));
717
718 // Load more notifications if the user has scrolled to the end of content
719 // item list.
195a683b 720 this.getContentContainer().on(CustomEvents.events.scrollBottom, function() {
a0e358a6
RW
721 if (!this.isLoading && !this.hasLoadedAllContent()) {
722 this.loadMoreNotifications();
723 }
724 }.bind(this));
725 };
726
727 return NotificationPopoverController;
728});