MDL-55623 core_message: indicate who the message is from
[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 = [];
296
297 if (notifications.length) {
298 $.each(notifications, function(index, notification) {
c5dd16a1
RW
299 notification.preferenceenabled = false;
300
301 // Check if we should display the preference block button.
302 if (notification.preference) {
303 var regexp = new RegExp(PROCESSOR_NAME);
304 if (notification.preference.loggedin.match(regexp) || notification.preference.loggedoff.match(regexp)) {
305 notification.preferenceenabled = true;
306 }
307 }
308
34eb5fcb 309 var promise = Templates.render('message/notification_content_item', notification);
a0e358a6
RW
310 promise.then(function(html, js) {
311 container.append(html);
34eb5fcb 312 Templates.runTemplateJS(js);
a0e358a6
RW
313 }.bind(this));
314
315 promises.push(promise);
316 }.bind(this));
317 }
318
319 return $.when.apply($.when, promises);
320 };
321
322 /**
323 * Send a request for more notifications from the server, if we aren't already
324 * loading some and haven't already loaded all of them.
325 *
326 * Takes into account the current mode of the popover and will request only
327 * unread notifications if required.
328 *
329 * All notifications are marked as read by the server when they are returned.
330 *
331 * @method loadMoreNotifications
332 * @return jQuery promise that is resolved when notifications have been
333 * retrieved and added to the DOM
334 */
335 NotificationPopoverController.prototype.loadMoreNotifications = function() {
336 if (this.isLoading || this.hasLoadedAllContent()) {
337 return $.Deferred().resolve();
338 }
339
340 this.startLoading();
341 var request = {
342 limit: this.limit,
343 offset: this.getOffset(),
344 useridto: this.userId,
345 markasread: true,
c5dd16a1 346 embedpreference: true,
a0e358a6
RW
347 embeduserto: false,
348 embeduserfrom: true,
349 };
350
351 if (this.unreadOnlyMode()) {
352 request.status = 'unread';
353 }
354
355 var container = this.getContent();
195a683b 356 var promise = NotificationRepo.query(request).then(function(result) {
a0e358a6
RW
357 var notifications = result.notifications;
358 this.unreadCount = result.unreadcount;
359 this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
360 this.getState().initialLoad = true;
361 this.updateButtonAriaLabel();
362
363 if (notifications.length) {
364 this.incrementOffset();
365 return this.renderNotifications(notifications, container);
366 }
367 }.bind(this))
368 .always(function() { this.stopLoading(); }.bind(this));
369
370 return promise;
371 };
372
373 /**
374 * Send a request to the server to mark all unread notifications as read and update
375 * the unread count and unread notification elements appropriately.
376 *
377 * @method markAllAsRead
378 */
379 NotificationPopoverController.prototype.markAllAsRead = function() {
380 this.markAllReadButton.addClass('loading');
381
195a683b 382 return NotificationRepo.markAllAsRead({useridto: this.userId})
a0e358a6
RW
383 .then(function() {
384 this.unreadCount = 0;
385 this.clearUnreadNotifications();
386 }.bind(this))
387 .always(function() { this.markAllReadButton.removeClass('loading'); }.bind(this));
388 };
389
390 /**
391 * Shift focus to the next content item in the list if the content item
392 * list current contains focus, otherwise the first item in the list is
393 * given focus.
394 *
d4555a3d 395 * Overrides PopoverRegionController.focusNextContentItem
a0e358a6
RW
396 * @method focusNextContentItem
397 */
398 NotificationPopoverController.prototype.focusNextContentItem = function() {
399 var currentFocus = $(document.activeElement);
400 var container = this.getContent();
401
402 if (container.has(currentFocus).length) {
403 var currentNotification = currentFocus.closest(SELECTORS.CONTENT_ITEM_CONTAINER);
404 currentNotification.next().focus();
405 } else {
406 this.focusFirstContentItem();
407 }
408 };
409
410 /**
411 * Shift focus to the previous content item in the content item list, if the
412 * content item list contains focus.
413 *
d4555a3d 414 * Overrides PopoverRegionController.focusPreviousContentItem
a0e358a6
RW
415 * @method focusPreviousContentItem
416 */
417 NotificationPopoverController.prototype.focusPreviousContentItem = function() {
418 var currentFocus = $(document.activeElement);
419 var container = this.getContent();
420
421 if (container.has(currentFocus).length) {
422 var currentNotification = currentFocus.closest(SELECTORS.CONTENT_ITEM_CONTAINER);
423 currentNotification.prev().focus();
424 }
425 };
426
427 /**
428 * Give focus to the first item in the list of content items.
429 *
d4555a3d 430 * Overrides PopoverRegionController.focusFirstContentItem
a0e358a6
RW
431 * @method focusFirstContentItem
432 */
433 NotificationPopoverController.prototype.focusFirstContentItem = function() {
434 var container = this.getContent();
435 var notification = container.children().first();
436
437 if (!notification.length) {
438 // If we don't have any notifications then we should focus the empty
439 // empty message for the user.
440 notification = container.next(SELECTORS.EMPTY_MESSAGE);
441 }
442
443 notification.focus();
444 };
445
446 /**
447 * Give focus to the last item in the list of content items, that is the list
448 * of notifications that have already been loaded.
449 *
d4555a3d 450 * Overrides PopoverRegionController.focusLastContentItem
a0e358a6
RW
451 * @method focusLastContentItem
452 */
453 NotificationPopoverController.prototype.focusLastContentItem = function() {
454 var container = this.getContent();
455 var notification = container.children().last();
456
457 if (!notification.length) {
458 // If we don't have any notifications then we should focus the empty
459 // empty message for the user.
460 notification = container.next(SELECTORS.EMPTY_MESSAGE);
461 }
462
463 notification.focus();
464 };
465
466 /**
467 * Expand all the currently rendered notificaitons in the current state
468 * of the popover (unread or all).
469 *
470 * @method expandAllContentItems
471 */
472 NotificationPopoverController.prototype.expandAllContentItems = function() {
473 this.getContent()
474 .find(SELECTORS.CONTENT_ITEM_CONTAINER)
475 .addClass('expanded')
476 .attr('aria-expanded', 'true');
477 };
478
479 /**
480 * Expand a single content item.
481 *
482 * @method expandContentItem
483 * @param item jQuery object the content item to be expanded
484 */
485 NotificationPopoverController.prototype.expandContentItem = function(item) {
486 item.addClass('expanded');
487 item.attr('aria-expanded', 'true');
488 item.find(SELECTORS.SHOW_BUTTON).attr('aria-hidden', 'true');
489 item.find(SELECTORS.CONTENT_BODY_SHORT).attr('aria-hidden', 'true');
490 item.find(SELECTORS.CONTENT_BODY_FULL).attr('aria-hidden', 'false');
491 item.find(SELECTORS.HIDE_BUTTON).attr('aria-hidden', 'false').focus();
492 };
493
494 /**
495 * Collapse a single content item.
496 *
497 * @method collapseContentItem
498 * @param item jQuery object the content item to be collapsed.
499 */
500 NotificationPopoverController.prototype.collapseContentItem = function(item) {
501 item.removeClass('expanded');
502 item.attr('aria-expanded', 'false');
503 item.find(SELECTORS.HIDE_BUTTON).attr('aria-hidden', 'true');
504 item.find(SELECTORS.CONTENT_BODY_FULL).attr('aria-hidden', 'true');
505 item.find(SELECTORS.CONTENT_BODY_SHORT).attr('aria-hidden', 'false');
506 item.find(SELECTORS.SHOW_BUTTON).attr('aria-hidden', 'false').focus();
507 };
508
c5dd16a1
RW
509 /**
510 * Remove the notification buttons for the given type of notification.
511 *
512 * @method removeDisableNotificationButtons
513 * @param type the type of notification to remove the button from
514 */
515 NotificationPopoverController.prototype.removeDisableNotificationButtons = function(type) {
516 this.root.find('[data-preference-key="'+type+'"]').remove();
517 };
518
519 /**
520 * Stop future notifications of this type appearing in the popover menu.
521 *
522 * @method disableNotificationType
523 * @param button jQuery object
524 */
525 NotificationPopoverController.prototype.disableNotificationType = function(button) {
526 if (button.hasClass('loading')) {
527 return $.Deferred();
528 }
529
530 button.addClass('loading');
531
532 var key = button.attr('data-preference-key');
533 var loggedin = button.attr('data-preference-loggedin');
534 var loggedoff = button.attr('data-preference-loggedoff');
535
536 // Remove the popup processor from the list.
537 loggedin = loggedin.split(',').filter(function(element) {
538 return element !== PROCESSOR_NAME;
539 }).join(',');
540
541 // Remove the popup processor from the list.
542 loggedoff = loggedoff.split(',').filter(function(element) {
543 return element !== PROCESSOR_NAME;
544 }).join(',');
545
546 // If no other processors are left then default to none.
547 if (loggedin === '') {
548 loggedin = 'none';
549 }
550
551 // If no other processors are left then default to none.
552 if (loggedoff === '') {
553 loggedoff = 'none';
554 }
555
556 var args = {
557 user: {
558 preferences: [
559 {
560 type: key + '_loggedin',
561 value: loggedin
562 },
563 {
564 type: key + '_loggedoff',
565 value: loggedoff
566 }
567 ]
568 }
569 };
570
571 var request = {
572 methodname: 'core_user_update_user',
573 args: args
574 };
575
34eb5fcb 576 var promise = Ajax.call([request])[0];
c5dd16a1 577
195a683b 578 promise.fail(DebugNotification.exception);
c5dd16a1
RW
579 promise.always(function() {
580 button.removeClass('loading');
581 });
582 promise.done(function() {
583 this.removeDisableNotificationButtons(key);
584 }.bind(this));
585
586 return promise;
587 };
588
a0e358a6
RW
589 /**
590 * Add all of the required event listeners for this notification popover.
591 *
592 * @method registerEventListeners
593 */
594 NotificationPopoverController.prototype.registerEventListeners = function() {
195a683b
RW
595 CustomEvents.define(this.root, [
596 CustomEvents.events.activate,
597 CustomEvents.events.keyboardActivate,
598 CustomEvents.events.next,
599 CustomEvents.events.previous,
600 CustomEvents.events.asterix,
a0e358a6
RW
601 ]);
602
603 // Expand the content item if the user activates (click/enter/space) the show
604 // button.
195a683b 605 this.root.on(CustomEvents.events.activate, SELECTORS.SHOW_BUTTON, function(e, data) {
a0e358a6
RW
606 var container = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
607 this.expandContentItem(container);
608
609 e.stopPropagation();
610 data.originalEvent.preventDefault();
611 }.bind(this));
612
613 // Expand the content item if the user triggers the next event (right arrow in LTR).
195a683b 614 this.root.on(CustomEvents.events.next, SELECTORS.CONTENT_ITEM_CONTAINER, function(e) {
a0e358a6
RW
615 var contentItem = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
616 this.expandContentItem(contentItem);
617 }.bind(this));
618
619 // Collapse the content item if the user activates the hide button.
195a683b 620 this.root.on(CustomEvents.events.activate, SELECTORS.HIDE_BUTTON, function(e, data) {
a0e358a6
RW
621 var container = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
622 this.collapseContentItem(container);
623
624 e.stopPropagation();
625 data.originalEvent.preventDefault();
626 }.bind(this));
627
628 // Collapse the content item if the user triggers the previous event (left arrow in LTR).
195a683b 629 this.root.on(CustomEvents.events.previous, SELECTORS.CONTENT_ITEM_CONTAINER, function(e) {
a0e358a6
RW
630 var contentItem = $(e.target).closest(SELECTORS.CONTENT_ITEM_CONTAINER);
631 this.collapseContentItem(contentItem);
632 }.bind(this));
633
195a683b 634 this.root.on(CustomEvents.events.activate, SELECTORS.BLOCK_BUTTON, function(e, data) {
c5dd16a1
RW
635 var button = $(e.target).closest(SELECTORS.BLOCK_BUTTON);
636 this.disableNotificationType(button);
637
638 e.stopPropagation();
639 data.originalEvent.preventDefault();
640 }.bind(this));
641
a0e358a6 642 // Switch between popover states (read/unread) if the user activates the toggle.
195a683b 643 this.root.on(CustomEvents.events.activate, SELECTORS.MODE_TOGGLE, function(e) {
a0e358a6
RW
644 if (this.modeToggle.hasClass('on')) {
645 this.clearUnreadNotifications();
646 this.modeToggle.removeClass('on');
647 this.modeToggle.addClass('off');
648 this.root.removeClass('unread-only');
649
34eb5fcb 650 Str.get_string('shownewnotifications', 'message').done(function(string) {
a0e358a6
RW
651 this.modeToggle.attr('aria-label', string);
652 }.bind(this));
653 } else {
654 this.modeToggle.removeClass('off');
655 this.modeToggle.addClass('on');
656 this.root.addClass('unread-only');
657
34eb5fcb 658 Str.get_string('showallnotifications', 'message').done(function(string) {
a0e358a6
RW
659 this.modeToggle.attr('aria-label', string);
660 }.bind(this));
661 }
662
663 if (!this.hasDoneInitialLoad()) {
664 this.loadMoreNotifications();
665 }
666
667 e.stopPropagation();
668 }.bind(this));
669
a0e358a6 670 // Mark all notifications read if the user activates the mark all as read button.
195a683b 671 this.root.on(CustomEvents.events.activate, SELECTORS.MARK_ALL_READ_BUTTON, function(e) {
a0e358a6
RW
672 this.markAllAsRead();
673 e.stopPropagation();
674 }.bind(this));
675
676 // Expand all the currently visible content items if the user hits the
677 // asterix key.
195a683b 678 this.root.on(CustomEvents.events.asterix, function() {
a0e358a6
RW
679 this.expandAllContentItems();
680 }.bind(this));
681
682 // Update the notification information when the menu is opened.
683 this.root.on(this.events().menuOpened, function() {
684 this.hideUnreadCount();
685 this.updateButtonAriaLabel();
686
687 if (!this.hasDoneInitialLoad()) {
688 this.loadMoreNotifications();
689 }
690 }.bind(this));
691
692 // Update the unread notification count when the menu is closed.
693 this.root.on(this.events().menuClosed, function() {
694 this.renderUnreadCount();
695 this.clearUnreadNotifications();
696 this.updateButtonAriaLabel();
697 }.bind(this));
698
699 // Set aria attributes when popover is loading.
700 this.root.on(this.events().startLoading, function() {
701 this.getContent().attr('aria-busy', 'true');
702 }.bind(this));
703
704 // Set aria attributes when popover is finished loading.
705 this.root.on(this.events().stopLoading, function() {
706 this.getContent().attr('aria-busy', 'false');
707 }.bind(this));
708
709 // Load more notifications if the user has scrolled to the end of content
710 // item list.
195a683b 711 this.getContentContainer().on(CustomEvents.events.scrollBottom, function() {
a0e358a6
RW
712 if (!this.isLoading && !this.hasLoadedAllContent()) {
713 this.loadMoreNotifications();
714 }
715 }.bind(this));
716 };
717
718 return NotificationPopoverController;
719});