Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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_popup/notification_popover
20
 *
21
 * @module     message_popup/notification_popover_controller
22
 * @class      notification_popover_controller
23
 * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
27
            'core/notification', 'core/custom_interaction_events', 'core/popover_region_controller',
28
            'message_popup/notification_repository', 'message_popup/notification_area_events'],
29
        function($, Ajax, Templates, Str, URL, DebugNotification, CustomEvents,
30
            PopoverController, NotificationRepo, NotificationAreaEvents) {
31
 
32
    var SELECTORS = {
33
        MARK_ALL_READ_BUTTON: '[data-action="mark-all-read"]',
34
        ALL_NOTIFICATIONS_CONTAINER: '[data-region="all-notifications"]',
35
        NOTIFICATION: '[data-region="notification-content-item-container"]',
36
        UNREAD_NOTIFICATION: '[data-region="notification-content-item-container"].unread',
37
        NOTIFICATION_LINK: '[data-action="content-item-link"]',
38
        EMPTY_MESSAGE: '[data-region="empty-message"]',
39
        COUNT_CONTAINER: '[data-region="count-container"]',
40
    };
41
 
42
    /**
43
     * Constructor for the NotificationPopoverController.
44
     * Extends PopoverRegionController.
45
     *
46
     * @param {object} element jQuery object root element of the popover
47
     */
48
    var NotificationPopoverController = function(element) {
49
        // Initialise base class.
50
        PopoverController.call(this, element);
51
 
52
        this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
53
        this.unreadCount = 0;
54
        this.lastQueried = 0;
55
        this.userId = this.root.attr('data-userid');
56
        this.container = this.root.find(SELECTORS.ALL_NOTIFICATIONS_CONTAINER);
57
        this.limit = 20;
58
        this.offset = 0;
59
        this.loadedAll = false;
60
        this.initialLoad = false;
61
 
62
        // Let's find out how many unread notifications there are.
63
        this.unreadCount = this.root.find(SELECTORS.COUNT_CONTAINER).html();
64
    };
65
 
66
    /**
67
     * Clone the parent prototype.
68
     */
69
    NotificationPopoverController.prototype = Object.create(PopoverController.prototype);
70
 
71
    /**
72
     * Make sure the constructor is set correctly.
73
     */
74
    NotificationPopoverController.prototype.constructor = NotificationPopoverController;
75
 
76
    /**
77
     * Set the correct aria label on the menu toggle button to be read out by screen
78
     * readers. The message will indicate the state of the unread notifications.
79
     *
80
     * @method updateButtonAriaLabel
81
     */
82
    NotificationPopoverController.prototype.updateButtonAriaLabel = function() {
83
        if (this.isMenuOpen()) {
84
            Str.get_string('hidenotificationwindow', 'message').done(function(string) {
85
                this.menuToggle.attr('aria-label', string);
86
            }.bind(this));
87
        } else {
88
            if (this.unreadCount) {
89
                Str.get_string('shownotificationwindowwithcount', 'message', this.unreadCount).done(function(string) {
90
                    this.menuToggle.attr('aria-label', string);
91
                }.bind(this));
92
            } else {
93
                Str.get_string('shownotificationwindownonew', 'message').done(function(string) {
94
                    this.menuToggle.attr('aria-label', string);
95
                }.bind(this));
96
            }
97
        }
98
    };
99
 
100
    /**
101
     * Return the jQuery element with the content. This will return either
102
     * the unread notification container or the all notification container
103
     * depending on which is currently visible.
104
     *
105
     * @method getContent
106
     * @return {object} jQuery object currently visible content contianer
107
     */
108
    NotificationPopoverController.prototype.getContent = function() {
109
        return this.container;
110
    };
111
 
112
    /**
113
     * Get the offset value for the current state of the popover in order
114
     * to sent to the backend to correctly paginate the notifications.
115
     *
116
     * @method getOffset
117
     * @return {int} current offset
118
     */
119
    NotificationPopoverController.prototype.getOffset = function() {
120
        return this.offset;
121
    };
122
 
123
    /**
124
     * Increment the offset for the current state, if required.
125
     *
126
     * @method incrementOffset
127
     */
128
    NotificationPopoverController.prototype.incrementOffset = function() {
129
        this.offset += this.limit;
130
    };
131
 
132
    /**
133
     * Check if the first load of notification has been triggered for the current
134
     * state of the popover.
135
     *
136
     * @method hasDoneInitialLoad
137
     * @return {bool} true if first notification loaded, false otherwise
138
     */
139
    NotificationPopoverController.prototype.hasDoneInitialLoad = function() {
140
        return this.initialLoad;
141
    };
142
 
143
    /**
144
     * Check if we've loaded all of the notifications for the current popover
145
     * state.
146
     *
147
     * @method hasLoadedAllContent
148
     * @return {bool} true if all notifications loaded, false otherwise
149
     */
150
    NotificationPopoverController.prototype.hasLoadedAllContent = function() {
151
        return this.loadedAll;
152
    };
153
 
154
    /**
155
     * Set the state of the loaded all content property for the current state
156
     * of the popover.
157
     *
158
     * @method setLoadedAllContent
159
     * @param {bool} val True if all content is loaded, false otherwise
160
     */
161
    NotificationPopoverController.prototype.setLoadedAllContent = function(val) {
162
        this.loadedAll = val;
163
    };
164
 
165
    /**
166
     * Show the unread notification count badge on the menu toggle if there
167
     * are unread notifications, otherwise hide it.
168
     *
169
     * @method renderUnreadCount
170
     */
171
    NotificationPopoverController.prototype.renderUnreadCount = function() {
172
        var element = this.root.find(SELECTORS.COUNT_CONTAINER);
173
 
174
        if (this.unreadCount) {
175
            element.text(this.unreadCount);
176
            element.removeClass('hidden');
177
        } else {
178
            element.addClass('hidden');
179
        }
180
    };
181
 
182
    /**
183
     * Hide the unread notification count badge on the menu toggle.
184
     *
185
     * @method hideUnreadCount
186
     */
187
    NotificationPopoverController.prototype.hideUnreadCount = function() {
188
        this.root.find(SELECTORS.COUNT_CONTAINER).addClass('hidden');
189
    };
190
 
191
    /**
192
     * Find the notification element for the given id.
193
     *
194
     * @param {int} id
195
     * @method getNotificationElement
196
     * @return {object|null} The notification element
197
     */
198
    NotificationPopoverController.prototype.getNotificationElement = function(id) {
199
        var element = this.root.find(SELECTORS.NOTIFICATION + '[data-id="' + id + '"]');
200
        return element.length == 1 ? element : null;
201
    };
202
 
203
    /**
204
     * Render the notification data with the appropriate template and add it to the DOM.
205
     *
206
     * @method renderNotifications
207
     * @param {array} notifications Notification data
208
     * @param {object} container jQuery object the container to append the rendered notifications
209
     * @return {object} jQuery promise that is resolved when all notifications have been
210
     *                  rendered and added to the DOM
211
     */
212
    NotificationPopoverController.prototype.renderNotifications = function(notifications, container) {
213
        var promises = [];
214
 
215
        $.each(notifications, function(index, notification) {
216
            // Determine what the offset was when loading this notification.
217
            var offset = this.getOffset() - this.limit;
218
            // Update the view more url to contain the offset to allow the notifications
219
            // page to load to the correct position in the list of notifications.
220
            notification.viewmoreurl = URL.relativeUrl('/message/output/popup/notifications.php', {
221
                notificationid: notification.id,
222
                offset: offset,
223
            });
224
 
225
            // Link to mark read page before loading the actual link.
226
            var notificationurlparams = {
227
                notificationid: notification.id
228
            };
229
 
230
            notification.contexturl = URL.relativeUrl('message/output/popup/mark_notification_read.php', notificationurlparams);
231
 
232
            var promise = Templates.render('message_popup/notification_content_item', notification)
233
            .then(function(html, js) {
234
                return {html: html, js: js};
235
            });
236
            promises.push(promise);
237
        }.bind(this));
238
 
239
        return $.when.apply($, promises).then(function() {
240
            // Each of the promises in the when will pass its results as an argument to the function.
241
            // The order of the arguments will be the order that the promises are passed to when()
242
            // i.e. the first promise's results will be in the first argument.
243
            $.each(arguments, function(index, argument) {
244
                container.append(argument.html);
245
                Templates.runTemplateJS(argument.js);
246
            });
247
            return;
248
        });
249
    };
250
 
251
    /**
252
     * Send a request for more notifications from the server, if we aren't already
253
     * loading some and haven't already loaded all of them.
254
     *
255
     * Takes into account the current mode of the popover and will request only
256
     * unread notifications if required.
257
     *
258
     * All notifications are marked as read by the server when they are returned.
259
     *
260
     * @method loadMoreNotifications
261
     * @return {object} jQuery promise that is resolved when notifications have been
262
     *                        retrieved and added to the DOM
263
     */
264
    NotificationPopoverController.prototype.loadMoreNotifications = function() {
265
        if (this.isLoading || this.hasLoadedAllContent()) {
266
            return $.Deferred().resolve();
267
        }
268
 
269
        this.startLoading();
270
        var request = {
271
            limit: this.limit,
272
            offset: this.getOffset(),
273
            useridto: this.userId,
274
        };
275
 
276
        var container = this.getContent();
277
        return NotificationRepo.query(request).then(function(result) {
278
            var notifications = result.notifications;
279
            this.unreadCount = result.unreadcount;
280
            this.lastQueried = Math.floor(new Date().getTime() / 1000);
281
            this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
282
            this.initialLoad = true;
283
            this.updateButtonAriaLabel();
284
 
285
            if (notifications.length) {
286
                this.incrementOffset();
287
                return this.renderNotifications(notifications, container);
288
            }
289
 
290
            return false;
291
        }.bind(this))
292
        .always(function() {
293
            this.stopLoading();
294
        }.bind(this));
295
    };
296
 
297
    /**
298
     * Send a request to the server to mark all unread notifications as read and update
299
     * the unread count and unread notification elements appropriately.
300
     *
301
     * @return {Promise}
302
     * @method markAllAsRead
303
     */
304
    NotificationPopoverController.prototype.markAllAsRead = function() {
305
        this.markAllReadButton.addClass('loading');
306
 
307
        var request = {
308
            useridto: this.userId,
309
            timecreatedto: this.lastQueried,
310
        };
311
 
312
        return NotificationRepo.markAllAsRead(request)
313
            .then(function() {
314
                this.unreadCount = 0;
315
                this.root.find(SELECTORS.UNREAD_NOTIFICATION).removeClass('unread');
316
            }.bind(this))
317
            .always(function() {
318
                this.markAllReadButton.removeClass('loading');
319
            }.bind(this));
320
    };
321
 
322
    /**
323
     * Add all of the required event listeners for this notification popover.
324
     *
325
     * @method registerEventListeners
326
     */
327
    NotificationPopoverController.prototype.registerEventListeners = function() {
328
        CustomEvents.define(this.root, [
329
            CustomEvents.events.activate,
330
        ]);
331
 
332
        // Mark all notifications read if the user activates the mark all as read button.
333
        this.root.on(CustomEvents.events.activate, SELECTORS.MARK_ALL_READ_BUTTON, function(e, data) {
334
            this.markAllAsRead();
335
            e.stopPropagation();
336
            data.originalEvent.preventDefault();
337
        }.bind(this));
338
 
339
        // Mark individual notification read if the user activates it.
340
        this.root.on(CustomEvents.events.activate, SELECTORS.NOTIFICATION_LINK, function(e) {
341
            var element = $(e.target).closest(SELECTORS.NOTIFICATION);
342
 
343
            if (element.hasClass('unread')) {
344
                this.unreadCount--;
345
                element.removeClass('unread');
346
            }
347
 
348
            e.stopPropagation();
349
        }.bind(this));
350
 
351
        // Update the notification information when the menu is opened.
352
        this.root.on(this.events().menuOpened, function() {
353
            this.hideUnreadCount();
354
            this.updateButtonAriaLabel();
355
 
356
            if (!this.hasDoneInitialLoad()) {
357
                this.loadMoreNotifications();
358
            }
359
        }.bind(this));
360
 
361
        // Update the unread notification count when the menu is closed.
362
        this.root.on(this.events().menuClosed, function() {
363
            this.renderUnreadCount();
364
            this.updateButtonAriaLabel();
365
        }.bind(this));
366
 
367
        // Set aria attributes when popover is loading.
368
        this.root.on(this.events().startLoading, function() {
369
            this.getContent().attr('aria-busy', 'true');
370
        }.bind(this));
371
 
372
        // Set aria attributes when popover is finished loading.
373
        this.root.on(this.events().stopLoading, function() {
374
            this.getContent().attr('aria-busy', 'false');
375
        }.bind(this));
376
 
377
        // Load more notifications if the user has scrolled to the end of content
378
        // item list.
379
        this.getContentContainer().on(CustomEvents.events.scrollBottom, function() {
380
            if (!this.isLoading && !this.hasLoadedAllContent()) {
381
                this.loadMoreNotifications();
382
            }
383
        }.bind(this));
384
 
385
        // Stop mouse scroll from propagating to the window element and
386
        // scrolling the page.
387
        CustomEvents.define(this.getContentContainer(), [
388
            CustomEvents.events.scrollLock
389
        ]);
390
 
391
        // Listen for when a notification is shown in the notifications page and mark
392
        // it as read, if it's unread.
393
        $(document).on(NotificationAreaEvents.notificationShown, function(e, notification) {
394
            if (!notification.read) {
395
                var element = this.getNotificationElement(notification.id);
396
 
397
                if (element) {
398
                    element.removeClass('unread');
399
                }
400
 
401
                this.unreadCount--;
402
                this.renderUnreadCount();
403
            }
404
        }.bind(this));
405
    };
406
 
407
    return NotificationPopoverController;
408
});