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