Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
 * Javascript to handle changing users via the user selector in the header.
18
 *
19
 * @module     mod_assign/grading_navigation
20
 * @copyright  2016 Damyon Wiese <damyon@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @since      3.1
23
 */
24
define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',
25
        'core/ajax', 'core_user/repository', 'mod_assign/grading_form_change_checker'],
26
       function($, notification, str, autocomplete, ajax, UserRepository, checker) {
27
 
28
    /**
29
     * GradingNavigation class.
30
     *
31
     * @class mod_assign/grading_navigation
32
     * @param {String} selector The selector for the page region containing the user navigation.
33
     */
34
    var GradingNavigation = function(selector) {
35
        this._regionSelector = selector;
36
        this._region = $(selector);
37
        this._filters = [];
38
        this._users = [];
39
        this._filteredUsers = [];
40
        this._lastXofYUpdate = 0;
41
        this._firstLoadUsers = true;
42
 
43
        let url = new URL(window.location);
44
        if (parseInt(url.searchParams.get('treset')) > 0) {
45
            // Remove 'treset' url parameter to make sure that
46
            // table preferences won't be reset on page refresh.
47
            url.searchParams.delete('treset');
48
            window.history.replaceState({}, "", url);
49
        }
50
 
51
        // Get the current user list from a webservice.
52
        this._loadAllUsers();
53
 
54
        // We do not allow navigation while ajax requests are pending.
55
        // Attach listeners to the select and arrow buttons.
56
 
57
        this._region.find('[data-action="previous-user"]').on('click', this._handlePreviousUser.bind(this));
58
        this._region.find('[data-action="next-user"]').on('click', this._handleNextUser.bind(this));
59
        this._region.find('[data-action="change-user"]').on('change', this._handleChangeUser.bind(this));
60
        this._region.find('[data-region="user-filters"]').on('click', this._toggleExpandFilters.bind(this));
61
        this._region.find('[data-region="user-resettable"]').on('click', this._toggleResetTable.bind());
62
 
63
        $(document).on('user-changed', this._refreshSelector.bind(this));
1441 ariadna 64
        $(document).on('reset-table', this._toggleResetTable.bind(this));
1 efrain 65
        $(document).on('done-saving-show-next', this._handleNextUser.bind(this));
66
 
67
        // Position the configure filters panel under the link that expands it.
68
        var toggleLink = this._region.find('[data-region="user-filters"]');
69
        var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
70
 
71
        configPanel.on('change', 'select', this._filterChanged.bind(this));
72
 
73
        var userid = $('[data-region="grading-navigation-panel"]').data('first-userid');
74
        if (userid) {
75
            this._selectUserById(userid);
76
        }
77
 
78
        str.get_string('changeuser', 'mod_assign').done(function(s) {
79
                autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);
80
            }
81
        ).fail(notification.exception);
82
 
83
        $(document).bind("start-loading-user", function() {
84
            this._isLoading = true;
85
        }.bind(this));
86
        $(document).bind("finish-loading-user", function() {
87
            this._isLoading = false;
88
        }.bind(this));
89
    };
90
 
91
    /** @property {Boolean} Boolean tracking active ajax requests. */
92
    GradingNavigation.prototype._isLoading = false;
93
 
94
    /** @property {String} Selector for the page region containing the user navigation. */
95
    GradingNavigation.prototype._regionSelector = null;
96
 
97
    /** @property {Array} The list of active filter keys */
98
    GradingNavigation.prototype._filters = null;
99
 
100
    /** @property {Array} The list of users */
101
    GradingNavigation.prototype._users = null;
102
 
103
    /** @property {JQuery} JQuery node for the page region containing the user navigation. */
104
    GradingNavigation.prototype._region = null;
105
 
106
    /** @property {String} Last active filters */
107
    GradingNavigation.prototype._lastFilters = '';
108
 
109
    /**
110
     * Load the list of all users for this assignment.
111
     *
112
     * @private
113
     * @method _loadAllUsers
114
     * @return {Boolean} True if the user list was fetched.
115
     */
116
    GradingNavigation.prototype._loadAllUsers = function() {
117
        var select = this._region.find('[data-action=change-user]');
118
        var assignmentid = select.attr('data-assignmentid');
119
        var groupid = select.attr('data-groupid');
120
 
121
        var filterPanel = this._region.find('[data-region="configure-filters"]');
122
        var filter = filterPanel.find('select[name="filter"]').val();
123
        var workflowFilter = filterPanel.find('select[name="workflowfilter"]');
124
        if (workflowFilter) {
125
            filter += ',' + workflowFilter.val();
126
        }
127
        var markerFilter = filterPanel.find('select[name="markerfilter"]');
128
        if (markerFilter) {
129
            filter += ',' + markerFilter.val();
130
        }
131
 
132
        if (this._lastFilters == filter) {
133
            return false;
134
        }
135
        this._lastFilters = filter;
136
 
137
        ajax.call([{
138
            methodname: 'mod_assign_list_participants',
139
            args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},
140
            done: this._usersLoaded.bind(this),
141
            fail: notification.exception
142
        }]);
143
        return true;
144
    };
145
 
146
    /**
147
     * Call back to rebuild the user selector and x of y info when the user list is updated.
148
     *
149
     * @private
150
     * @method _usersLoaded
151
     * @param {Array} users
152
     */
153
    GradingNavigation.prototype._usersLoaded = function(users) {
154
        this._firstLoadUsers = false;
155
        this._filteredUsers = this._users = users;
156
        if (this._users.length) {
157
            // Position the configure filters panel under the link that expands it.
158
            var toggleLink = this._region.find('[data-region="user-filters"]');
159
            var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
160
 
161
            configPanel.find('select[name="filter"]').trigger('change');
1441 ariadna 162
 
163
            $('[data-region="grade-panel"]').show();
164
            $('[data-region="grade-actions-panel"]').show();
1 efrain 165
        } else {
166
            this._selectNoUser();
167
        }
168
        this._triggerNextUserEvent();
169
    };
170
 
171
    /**
172
     * Close the configure filters panel if a click is detected outside of it.
173
     *
174
     * @private
175
     * @method _checkClickOutsideConfigureFilters
176
     * @param {Event} event
177
     */
178
    GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {
179
        var configPanel = this._region.find('[data-region="configure-filters"]');
180
 
181
        if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {
182
            var toggleLink = this._region.find('[data-region="user-filters"]');
183
 
184
            configPanel.hide();
185
            configPanel.attr('aria-hidden', 'true');
186
            toggleLink.attr('aria-expanded', 'false');
187
            $(document).unbind('click.mod_assign_grading_navigation');
188
        }
189
    };
190
 
191
    /**
192
     * Close the configure filters panel if a click is detected outside of it.
193
     *
194
     * @private
195
     * @method _updateFilterPreference
196
     * @param {Number} userId The current user id.
197
     * @param {Array} filterList The list of current filter values.
198
     * @param {Array} preferenceNames The names of the preferences to update
199
     * @return {Promise} Resolved when all the preferences are updated.
200
     */
201
    GradingNavigation.prototype._updateFilterPreferences = function(userId, filterList, preferenceNames) {
202
        var preferences = [],
203
            i = 0;
204
 
205
        if (filterList.length == 0 || this._firstLoadUsers) {
206
            // Nothing to update.
207
            var deferred = $.Deferred();
208
            deferred.resolve();
209
            return deferred;
210
        }
211
        // General filter.
212
        // Set the user preferences to the current filters.
213
        for (i = 0; i < filterList.length; i++) {
214
            var newValue = filterList[i];
215
            if (newValue == 'none') {
216
                newValue = '';
217
            }
218
 
219
            preferences.push({
220
                userid: userId,
221
                name: preferenceNames[i],
222
                value: newValue
223
            });
224
        }
225
 
226
        return UserRepository.setUserPreferences(preferences);
227
    };
228
    /**
229
     * Turn a filter on or off.
230
     *
231
     * @private
232
     * @method _filterChanged
233
     */
234
    GradingNavigation.prototype._filterChanged = function() {
235
        // There are 3 types of filter right now.
236
        var filterPanel = this._region.find('[data-region="configure-filters"]');
237
        var filters = filterPanel.find('select');
238
        var preferenceNames = [];
239
 
240
        this._filters = [];
241
        filters.each(function(idx, ele) {
242
            var element = $(ele);
243
            this._filters.push(element.val());
244
            preferenceNames.push('assign_' + element.prop('name'));
245
        }.bind(this));
246
 
247
        // Update the active filter string.
248
        var filterlist = [];
249
        filterPanel.find('option:checked').each(function(idx, ele) {
250
            filterlist[filterlist.length] = $(ele).text();
251
        });
252
        if (filterlist.length) {
253
            this._region.find('[data-region="user-filters"] span').text(filterlist.join(', '));
254
        } else {
255
            str.get_string('nofilters', 'mod_assign').done(function(s) {
256
                this._region.find('[data-region="user-filters"] span').text(s);
257
            }.bind(this)).fail(notification.exception);
258
        }
259
 
260
        var select = this._region.find('[data-action=change-user]');
261
        var currentUserID = select.data('currentuserid');
1441 ariadna 262
        this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).then(function() {
1 efrain 263
            // Reload the list of users to apply the new filters.
264
            if (!this._loadAllUsers()) {
265
                var userid = parseInt(select.attr('data-selected'));
266
                let foundIndex = null;
267
                // Search the returned users for the current selection.
268
                $.each(this._filteredUsers, function(index, user) {
269
                    if (userid == user.id) {
270
                        foundIndex = index;
271
                    }
272
                });
273
 
1441 ariadna 274
                if (this._filteredUsers.length) {
275
                    this._selectUserById(this._filteredUsers[foundIndex ?? 0].id);
1 efrain 276
                } else {
277
                    this._selectNoUser();
278
                }
279
 
280
            }
1441 ariadna 281
        }.bind(this)).catch(notification.exception);
1 efrain 282
        this._refreshCount();
283
    };
284
 
285
    /**
286
     * Select no users, because no users match the filters.
287
     *
288
     * @private
289
     * @method _selectNoUser
290
     */
291
    GradingNavigation.prototype._selectNoUser = function() {
292
        // Detect unsaved changes, and offer to save them - otherwise change user right now.
293
        if (this._isLoading) {
294
            return;
295
        }
1441 ariadna 296
 
297
        $('[data-region="grade-panel"]').hide();
298
        $('[data-region="grade-actions-panel"]').hide();
299
 
1 efrain 300
        if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
301
            // Form has changes, so we need to confirm before switching users.
302
            str.get_strings([
303
                {key: 'unsavedchanges', component: 'mod_assign'},
304
                {key: 'unsavedchangesquestion', component: 'mod_assign'},
305
                {key: 'saveandcontinue', component: 'mod_assign'},
306
                {key: 'cancel', component: 'core'},
307
            ]).done(function(strs) {
308
                notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
309
                    $(document).trigger('save-changes', -1);
310
                });
311
            });
312
        } else {
313
            $(document).trigger('user-changed', -1);
314
        }
315
    };
316
 
317
    /**
318
     * Select the specified user by id.
319
     *
320
     * @private
321
     * @method _selectUserById
322
     * @param {Number} userid
323
     */
324
    GradingNavigation.prototype._selectUserById = function(userid) {
325
        var select = this._region.find('[data-action=change-user]');
326
        var useridnumber = parseInt(userid, 10);
327
 
328
        // Detect unsaved changes, and offer to save them - otherwise change user right now.
329
        if (this._isLoading) {
330
            return;
331
        }
332
        if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
333
            // Form has changes, so we need to confirm before switching users.
334
            str.get_strings([
335
                {key: 'unsavedchanges', component: 'mod_assign'},
336
                {key: 'unsavedchangesquestion', component: 'mod_assign'},
337
                {key: 'saveandcontinue', component: 'mod_assign'},
338
                {key: 'cancel', component: 'core'},
339
            ]).done(function(strs) {
340
                notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
341
                    $(document).trigger('save-changes', useridnumber);
342
                });
343
            });
344
        } else {
345
            select.attr('data-selected', userid);
346
 
347
            // If we have some filtered users, and userid is specified, then trigger change.
348
            if (this._filteredUsers.length > 0 && !isNaN(useridnumber) && useridnumber > 0) {
349
                $(document).trigger('user-changed', useridnumber);
350
            }
351
        }
352
    };
353
 
354
    /**
355
     * Expand or collapse the filter config panel.
356
     *
357
     * @private
358
     * @method _toggleExpandFilters
359
     * @param {Event} event
360
     */
361
    GradingNavigation.prototype._toggleExpandFilters = function(event) {
362
        event.preventDefault();
363
        var toggleLink = $(event.target).closest('[data-region="user-filters"]');
364
        var expanded = toggleLink.attr('aria-expanded') == 'true';
365
        var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));
366
 
367
        if (expanded) {
368
            configPanel.hide();
369
            configPanel.attr('aria-hidden', 'true');
370
            toggleLink.attr('aria-expanded', 'false');
371
            $(document).unbind('click.mod_assign_grading_navigation');
372
        } else {
373
            configPanel.css('display', 'inline-block');
374
            configPanel.attr('aria-hidden', 'false');
375
            toggleLink.attr('aria-expanded', 'true');
376
            event.stopPropagation();
377
            $(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));
378
        }
379
    };
380
 
381
    /**
382
     * Reset table preferences.
383
     *
384
     * @private
385
     * @method _toggleResetTable
386
     */
387
    GradingNavigation.prototype._toggleResetTable = function() {
388
        let url = new URL(window.location);
389
        url.searchParams.set('treset', '1');
390
        window.location.href = url;
391
    };
392
 
393
    /**
394
     * Change to the previous user in the grading list.
395
     *
396
     * @private
397
     * @method _handlePreviousUser
398
     * @param {Event} e
399
     */
400
    GradingNavigation.prototype._handlePreviousUser = function(e) {
401
        e.preventDefault();
402
        var select = this._region.find('[data-action=change-user]');
403
        var currentUserId = select.attr('data-selected');
404
        var i = 0;
405
        var currentIndex = 0;
406
 
407
        for (i = 0; i < this._filteredUsers.length; i++) {
408
            if (this._filteredUsers[i].id == currentUserId) {
409
                currentIndex = i;
410
                break;
411
            }
412
        }
413
 
414
        var count = this._filteredUsers.length;
415
        var newIndex = (currentIndex - 1);
416
        if (newIndex < 0) {
417
            newIndex = count - 1;
418
        }
419
 
420
        if (count) {
421
            this._selectUserById(this._filteredUsers[newIndex].id);
422
        }
423
    };
424
 
425
    /**
426
     * Change to the next user in the grading list.
427
     *
428
     * @param {Event} e
429
     * @param {Boolean} saved Has the form already been saved? Skips checking for changes if true.
430
     */
431
    GradingNavigation.prototype._handleNextUser = function(e, saved) {
432
        e.preventDefault();
433
        var select = this._region.find('[data-action=change-user]');
434
        var currentUserId = select.attr('data-selected');
435
        var i = 0;
436
        var currentIndex = 0;
437
 
438
        for (i = 0; i < this._filteredUsers.length; i++) {
439
            if (this._filteredUsers[i].id == currentUserId) {
440
                currentIndex = i;
441
                break;
442
            }
443
        }
444
 
445
        var count = this._filteredUsers.length;
446
        var newIndex = (currentIndex + 1) % count;
447
 
448
        if (saved && count) {
449
            // If we've already saved the grade, skip checking if we've made any changes.
450
            var userid = this._filteredUsers[newIndex].id;
451
            var useridnumber = parseInt(userid, 10);
452
            select.attr('data-selected', userid);
453
            if (!isNaN(useridnumber) && useridnumber > 0) {
454
                $(document).trigger('user-changed', userid);
455
            }
456
        } else if (count) {
457
            this._selectUserById(this._filteredUsers[newIndex].id);
458
        }
459
    };
460
 
461
    /**
462
     * Set count string. This method only sets the value for the last time it was ever called to deal
463
     * with promises that return in a non-predictable order.
464
     *
465
     * @private
466
     * @method _setCountString
467
     * @param {Number} x
468
     * @param {Number} y
469
     */
470
    GradingNavigation.prototype._setCountString = function(x, y) {
471
        var updateNumber = 0;
472
        this._lastXofYUpdate++;
473
        updateNumber = this._lastXofYUpdate;
474
 
475
        var param = {x: x, y: y};
476
        str.get_string('xofy', 'mod_assign', param).done(function(s) {
477
            if (updateNumber == this._lastXofYUpdate) {
478
                this._region.find('[data-region="user-count-summary"]').text(s);
479
            }
480
        }.bind(this)).fail(notification.exception);
481
    };
482
 
483
    /**
484
     * Rebuild the x of y string.
485
     *
486
     * @private
487
     * @method _refreshCount
488
     */
489
    GradingNavigation.prototype._refreshCount = function() {
490
        var select = this._region.find('[data-action=change-user]');
491
        var userid = select.attr('data-selected');
492
        var i = 0;
493
        var currentIndex = 0;
494
 
495
        if (isNaN(userid) || userid <= 0) {
496
            this._region.find('[data-region="user-count"]').hide();
497
        } else {
498
            this._region.find('[data-region="user-count"]').show();
499
 
500
            for (i = 0; i < this._filteredUsers.length; i++) {
501
                if (this._filteredUsers[i].id == userid) {
502
                    currentIndex = i;
503
                    break;
504
                }
505
            }
506
            var count = this._filteredUsers.length;
507
            if (count) {
508
                currentIndex += 1;
509
            }
510
            this._setCountString(currentIndex, count);
511
            // Update window URL
512
            if (currentIndex > 0) {
513
                var url = new URL(window.location);
514
                if (parseInt(url.searchParams.get('blindid')) > 0) {
515
                    var newid = this._filteredUsers[currentIndex - 1].recordid;
516
                    url.searchParams.set('blindid', newid);
517
                } else {
518
                    url.searchParams.set('userid', userid);
519
                }
520
                // We do this so a browser refresh will return to the same user.
521
                window.history.replaceState({}, "", url);
522
            }
523
        }
524
    };
525
 
526
    /**
527
     * Respond to a user-changed event by updating the selector.
528
     *
529
     * @private
530
     * @method _refreshSelector
531
     * @param {Event} event
532
     * @param {String} userid
533
     */
534
    GradingNavigation.prototype._refreshSelector = function(event, userid) {
535
        var select = this._region.find('[data-action=change-user]');
536
        userid = parseInt(userid, 10);
537
 
538
        if (!isNaN(userid) && userid > 0) {
539
            select.attr('data-selected', userid);
540
        }
541
        this._refreshCount();
542
    };
543
 
544
    /**
545
     * Trigger the next user event depending on the number of filtered users
546
     *
547
     * @private
548
     * @method _triggerNextUserEvent
549
     */
550
    GradingNavigation.prototype._triggerNextUserEvent = function() {
551
        if (this._filteredUsers.length > 1) {
552
            $(document).trigger('next-user', {nextUserId: null, nextUser: true});
553
        } else {
554
            $(document).trigger('next-user', {nextUser: false});
555
        }
556
    };
557
 
558
    /**
559
     * Change to a different user in the grading list.
560
     *
561
     * @private
562
     * @method _handleChangeUser
563
     */
564
    GradingNavigation.prototype._handleChangeUser = function() {
565
        var select = this._region.find('[data-action=change-user]');
566
        var userid = parseInt(select.val(), 10);
567
 
568
        if (this._isLoading) {
569
            return;
570
        }
571
        if (checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')) {
572
            // Form has changes, so we need to confirm before switching users.
573
            str.get_strings([
574
                {key: 'unsavedchanges', component: 'mod_assign'},
575
                {key: 'unsavedchangesquestion', component: 'mod_assign'},
576
                {key: 'saveandcontinue', component: 'mod_assign'},
577
                {key: 'cancel', component: 'core'},
578
            ]).done(function(strs) {
579
                notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
580
                    $(document).trigger('save-changes', userid);
581
                });
582
            });
583
        } else {
584
            if (!isNaN(userid) && userid > 0) {
585
                select.attr('data-selected', userid);
586
 
587
                $(document).trigger('user-changed', userid);
588
            }
589
        }
590
    };
591
 
592
    return GradingNavigation;
593
});