From 6684ca3da779bb7b29fb8e4bb3ae5b2c8f06c65f Mon Sep 17 00:00:00 2001 From: Mikhail Golenkov Date: Tue, 14 Jun 2022 19:04:06 +1000 Subject: [PATCH] MDL-74234 assign: Fix grading page for filtered out users --- lang/en/error.php | 1 + .../amd/build/grading_navigation.min.js | 2 +- .../amd/build/grading_navigation.min.js.map | 2 +- mod/assign/amd/src/grading_navigation.js | 25 ++++++- mod/assign/externallib.php | 6 ++ mod/assign/lib.php | 8 +++ mod/assign/locallib.php | 15 +++++ .../grading_navigation_user_selector.mustache | 6 ++ .../behat/assign_table_preferences.feature | 57 ++++++++++++++++ mod/assign/tests/behat/filter_drafts.feature | 3 + mod/assign/tests/locallib_test.php | 67 +++++++++++++++++++ mod/assign/upgrade.txt | 4 ++ 12 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 mod/assign/tests/behat/assign_table_preferences.feature diff --git a/lang/en/error.php b/lang/en/error.php index da5e7b2e221..114fe7d1271 100644 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -599,6 +599,7 @@ $string['useradminodelete'] = 'Administrator accounts cannot be deleted.'; $string['userautherror'] = 'Unknown auth plugin'; $string['userauthunsupported'] = 'Auth plugin not supported here'; $string['useremailduplicate'] = 'Duplicate address'; +$string['userisfilteredout'] = 'This user does not match filters and table preferences!'; $string['usermustbemnet'] = 'Users in the MNET access control list must be remote MNET users'; $string['usernamelowercase'] = 'The username must be in lower case'; $string['usernotaddederror'] = 'User not added - error'; diff --git a/mod/assign/amd/build/grading_navigation.min.js b/mod/assign/amd/build/grading_navigation.min.js index cd1150dc3ff..71a11bb876c 100644 --- a/mod/assign/amd/build/grading_navigation.min.js +++ b/mod/assign/amd/build/grading_navigation.min.js @@ -6,6 +6,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.1 */ -define("mod_assign/grading_navigation",["jquery","core/notification","core/str","core/form-autocomplete","core/ajax","mod_assign/grading_form_change_checker"],(function($,notification,str,autocomplete,ajax,checker){var GradingNavigation=function(selector){this._regionSelector=selector,this._region=$(selector),this._filters=[],this._users=[],this._filteredUsers=[],this._lastXofYUpdate=0,this._firstLoadUsers=!0,this._loadAllUsers(),this._region.find('[data-action="previous-user"]').on("click",this._handlePreviousUser.bind(this)),this._region.find('[data-action="next-user"]').on("click",this._handleNextUser.bind(this)),this._region.find('[data-action="change-user"]').on("change",this._handleChangeUser.bind(this)),this._region.find('[data-region="user-filters"]').on("click",this._toggleExpandFilters.bind(this)),$(document).on("user-changed",this._refreshSelector.bind(this)),$(document).on("done-saving-show-next",this._handleNextUser.bind(this));var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).on("change","select",this._filterChanged.bind(this));var userid=$('[data-region="grading-navigation-panel"]').data("first-userid");userid&&this._selectUserById(userid),str.get_string("changeuser","mod_assign").done((function(s){autocomplete.enhance("[data-action=change-user]",!1,"mod_assign/participant_selector",s)})).fail(notification.exception),$(document).bind("start-loading-user",function(){this._isLoading=!0}.bind(this)),$(document).bind("finish-loading-user",function(){this._isLoading=!1}.bind(this))};return GradingNavigation.prototype._isLoading=!1,GradingNavigation.prototype._regionSelector=null,GradingNavigation.prototype._filters=null,GradingNavigation.prototype._users=null,GradingNavigation.prototype._region=null,GradingNavigation.prototype._lastFilters="",GradingNavigation.prototype._loadAllUsers=function(){var select=this._region.find("[data-action=change-user]"),assignmentid=select.attr("data-assignmentid"),groupid=select.attr("data-groupid"),filterPanel=this._region.find('[data-region="configure-filters"]'),filter=filterPanel.find('select[name="filter"]').val(),workflowFilter=filterPanel.find('select[name="workflowfilter"]');workflowFilter&&(filter+=","+workflowFilter.val());var markerFilter=filterPanel.find('select[name="markerfilter"]');return markerFilter&&(filter+=","+markerFilter.val()),this._lastFilters!=filter&&(this._lastFilters=filter,ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:"",onlyids:!0,tablesort:!0},done:this._usersLoaded.bind(this),fail:notification.exception}]),!0)},GradingNavigation.prototype._usersLoaded=function(users){if(this._firstLoadUsers=!1,this._filteredUsers=this._users=users,this._users.length){var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).find('select[name="filter"]').trigger("change")}else this._selectNoUser();this._triggerNextUserEvent()},GradingNavigation.prototype._checkClickOutsideConfigureFilters=function(event){var configPanel=this._region.find('[data-region="configure-filters"]');if(!configPanel.is(event.target)&&0===configPanel.has(event.target).length){var toggleLink=this._region.find('[data-region="user-filters"]');configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")}},GradingNavigation.prototype._updateFilterPreferences=function(userId,filterList,preferenceNames){var preferences=[],i=0;if(0==filterList.length||this._firstLoadUsers){var deferred=$.Deferred();return deferred.resolve(),deferred}for(i=0;i0&&$(document).trigger("user-changed",userid)))},GradingNavigation.prototype._toggleExpandFilters=function(event){event.preventDefault();var toggleLink=$(event.target).closest('[data-region="user-filters"]'),expanded="true"==toggleLink.attr("aria-expanded"),configPanel=$(document.getElementById(toggleLink.attr("aria-controls")));expanded?(configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")):(configPanel.css("display","inline-block"),configPanel.attr("aria-hidden","false"),toggleLink.attr("aria-expanded","true"),event.stopPropagation(),$(document).on("click.mod_assign_grading_navigation",this._checkClickOutsideConfigureFilters.bind(this)))},GradingNavigation.prototype._handlePreviousUser=function(e){e.preventDefault();var currentUserId=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;for(i=0;i0&&$(document).trigger("user-changed",userid)}else count&&this._selectUserById(this._filteredUsers[newIndex].id)},GradingNavigation.prototype._setCountString=function(x,y){var updateNumber;this._lastXofYUpdate++,updateNumber=this._lastXofYUpdate;var param={x:x,y:y};str.get_string("xofy","mod_assign",param).done(function(s){updateNumber==this._lastXofYUpdate&&this._region.find('[data-region="user-count-summary"]').text(s)}.bind(this)).fail(notification.exception)},GradingNavigation.prototype._refreshCount=function(){var userid=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;if(isNaN(userid)||userid<=0)this._region.find('[data-region="user-count"]').hide();else{for(this._region.find('[data-region="user-count"]').show(),i=0;i0){var url=new URL(window.location);if(parseInt(url.searchParams.get("blindid"))>0){var newid=this._filteredUsers[currentIndex-1].recordid;url.searchParams.set("blindid",newid)}else url.searchParams.set("userid",userid);window.history.replaceState({},"",url)}}},GradingNavigation.prototype._refreshSelector=function(event,userid){var select=this._region.find("[data-action=change-user]");userid=parseInt(userid,10),!isNaN(userid)&&userid>0&&select.attr("data-selected",userid),this._refreshCount()},GradingNavigation.prototype._triggerNextUserEvent=function(){this._filteredUsers.length>1?$(document).trigger("next-user",{nextUserId:null,nextUser:!0}):$(document).trigger("next-user",{nextUser:!1})},GradingNavigation.prototype._handleChangeUser=function(){var select=this._region.find("[data-action=change-user]"),userid=parseInt(select.val(),10);this._isLoading||(checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')?str.get_strings([{key:"unsavedchanges",component:"mod_assign"},{key:"unsavedchangesquestion",component:"mod_assign"},{key:"saveandcontinue",component:"mod_assign"},{key:"cancel",component:"core"}]).done((function(strs){notification.confirm(strs[0],strs[1],strs[2],strs[3],(function(){$(document).trigger("save-changes",userid)}))})):!isNaN(userid)&&userid>0&&(select.attr("data-selected",userid),$(document).trigger("user-changed",userid)))},GradingNavigation})); +define("mod_assign/grading_navigation",["jquery","core/notification","core/str","core/form-autocomplete","core/ajax","mod_assign/grading_form_change_checker"],(function($,notification,str,autocomplete,ajax,checker){var GradingNavigation=function(selector){this._regionSelector=selector,this._region=$(selector),this._filters=[],this._users=[],this._filteredUsers=[],this._lastXofYUpdate=0,this._firstLoadUsers=!0;let url=new URL(window.location);parseInt(url.searchParams.get("treset"))>0&&(url.searchParams.delete("treset"),window.history.replaceState({},"",url)),this._loadAllUsers(),this._region.find('[data-action="previous-user"]').on("click",this._handlePreviousUser.bind(this)),this._region.find('[data-action="next-user"]').on("click",this._handleNextUser.bind(this)),this._region.find('[data-action="change-user"]').on("change",this._handleChangeUser.bind(this)),this._region.find('[data-region="user-filters"]').on("click",this._toggleExpandFilters.bind(this)),this._region.find('[data-region="user-resettable"]').on("click",this._toggleResetTable.bind()),$(document).on("user-changed",this._refreshSelector.bind(this)),$(document).on("done-saving-show-next",this._handleNextUser.bind(this));var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).on("change","select",this._filterChanged.bind(this));var userid=$('[data-region="grading-navigation-panel"]').data("first-userid");userid&&this._selectUserById(userid),str.get_string("changeuser","mod_assign").done((function(s){autocomplete.enhance("[data-action=change-user]",!1,"mod_assign/participant_selector",s)})).fail(notification.exception),$(document).bind("start-loading-user",function(){this._isLoading=!0}.bind(this)),$(document).bind("finish-loading-user",function(){this._isLoading=!1}.bind(this))};return GradingNavigation.prototype._isLoading=!1,GradingNavigation.prototype._regionSelector=null,GradingNavigation.prototype._filters=null,GradingNavigation.prototype._users=null,GradingNavigation.prototype._region=null,GradingNavigation.prototype._lastFilters="",GradingNavigation.prototype._loadAllUsers=function(){var select=this._region.find("[data-action=change-user]"),assignmentid=select.attr("data-assignmentid"),groupid=select.attr("data-groupid"),filterPanel=this._region.find('[data-region="configure-filters"]'),filter=filterPanel.find('select[name="filter"]').val(),workflowFilter=filterPanel.find('select[name="workflowfilter"]');workflowFilter&&(filter+=","+workflowFilter.val());var markerFilter=filterPanel.find('select[name="markerfilter"]');return markerFilter&&(filter+=","+markerFilter.val()),this._lastFilters!=filter&&(this._lastFilters=filter,ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:"",onlyids:!0,tablesort:!0},done:this._usersLoaded.bind(this),fail:notification.exception}]),!0)},GradingNavigation.prototype._usersLoaded=function(users){if(this._firstLoadUsers=!1,this._filteredUsers=this._users=users,this._users.length){var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).find('select[name="filter"]').trigger("change")}else this._selectNoUser();this._triggerNextUserEvent()},GradingNavigation.prototype._checkClickOutsideConfigureFilters=function(event){var configPanel=this._region.find('[data-region="configure-filters"]');if(!configPanel.is(event.target)&&0===configPanel.has(event.target).length){var toggleLink=this._region.find('[data-region="user-filters"]');configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")}},GradingNavigation.prototype._updateFilterPreferences=function(userId,filterList,preferenceNames){var preferences=[],i=0;if(0==filterList.length||this._firstLoadUsers){var deferred=$.Deferred();return deferred.resolve(),deferred}for(i=0;i0&&$(document).trigger("user-changed",userid)))},GradingNavigation.prototype._toggleExpandFilters=function(event){event.preventDefault();var toggleLink=$(event.target).closest('[data-region="user-filters"]'),expanded="true"==toggleLink.attr("aria-expanded"),configPanel=$(document.getElementById(toggleLink.attr("aria-controls")));expanded?(configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")):(configPanel.css("display","inline-block"),configPanel.attr("aria-hidden","false"),toggleLink.attr("aria-expanded","true"),event.stopPropagation(),$(document).on("click.mod_assign_grading_navigation",this._checkClickOutsideConfigureFilters.bind(this)))},GradingNavigation.prototype._toggleResetTable=function(){let url=new URL(window.location);url.searchParams.set("treset","1"),window.location.href=url},GradingNavigation.prototype._handlePreviousUser=function(e){e.preventDefault();var currentUserId=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;for(i=0;i0&&$(document).trigger("user-changed",userid)}else count&&this._selectUserById(this._filteredUsers[newIndex].id)},GradingNavigation.prototype._setCountString=function(x,y){var updateNumber;this._lastXofYUpdate++,updateNumber=this._lastXofYUpdate;var param={x:x,y:y};str.get_string("xofy","mod_assign",param).done(function(s){updateNumber==this._lastXofYUpdate&&this._region.find('[data-region="user-count-summary"]').text(s)}.bind(this)).fail(notification.exception)},GradingNavigation.prototype._refreshCount=function(){var userid=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;if(isNaN(userid)||userid<=0)this._region.find('[data-region="user-count"]').hide();else{for(this._region.find('[data-region="user-count"]').show(),i=0;i0){var url=new URL(window.location);if(parseInt(url.searchParams.get("blindid"))>0){var newid=this._filteredUsers[currentIndex-1].recordid;url.searchParams.set("blindid",newid)}else url.searchParams.set("userid",userid);window.history.replaceState({},"",url)}}},GradingNavigation.prototype._refreshSelector=function(event,userid){var select=this._region.find("[data-action=change-user]");userid=parseInt(userid,10),!isNaN(userid)&&userid>0&&select.attr("data-selected",userid),this._refreshCount()},GradingNavigation.prototype._triggerNextUserEvent=function(){this._filteredUsers.length>1?$(document).trigger("next-user",{nextUserId:null,nextUser:!0}):$(document).trigger("next-user",{nextUser:!1})},GradingNavigation.prototype._handleChangeUser=function(){var select=this._region.find("[data-action=change-user]"),userid=parseInt(select.val(),10);this._isLoading||(checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')?str.get_strings([{key:"unsavedchanges",component:"mod_assign"},{key:"unsavedchangesquestion",component:"mod_assign"},{key:"saveandcontinue",component:"mod_assign"},{key:"cancel",component:"core"}]).done((function(strs){notification.confirm(strs[0],strs[1],strs[2],strs[3],(function(){$(document).trigger("save-changes",userid)}))})):!isNaN(userid)&&userid>0&&(select.attr("data-selected",userid),$(document).trigger("user-changed",userid)))},GradingNavigation})); //# sourceMappingURL=grading_navigation.min.js.map \ No newline at end of file diff --git a/mod/assign/amd/build/grading_navigation.min.js.map b/mod/assign/amd/build/grading_navigation.min.js.map index 07240430ca0..52dec787aae 100644 --- a/mod/assign/amd/build/grading_navigation.min.js.map +++ b/mod/assign/amd/build/grading_navigation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"grading_navigation.min.js","sources":["../src/grading_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to handle changing users via the user selector in the header.\n *\n * @module mod_assign/grading_navigation\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',\n 'core/ajax', 'mod_assign/grading_form_change_checker'],\n function($, notification, str, autocomplete, ajax, checker) {\n\n /**\n * GradingNavigation class.\n *\n * @class mod_assign/grading_navigation\n * @param {String} selector The selector for the page region containing the user navigation.\n */\n var GradingNavigation = function(selector) {\n this._regionSelector = selector;\n this._region = $(selector);\n this._filters = [];\n this._users = [];\n this._filteredUsers = [];\n this._lastXofYUpdate = 0;\n this._firstLoadUsers = true;\n\n // Get the current user list from a webservice.\n this._loadAllUsers();\n\n // We do not allow navigation while ajax requests are pending.\n // Attach listeners to the select and arrow buttons.\n\n this._region.find('[data-action=\"previous-user\"]').on('click', this._handlePreviousUser.bind(this));\n this._region.find('[data-action=\"next-user\"]').on('click', this._handleNextUser.bind(this));\n this._region.find('[data-action=\"change-user\"]').on('change', this._handleChangeUser.bind(this));\n this._region.find('[data-region=\"user-filters\"]').on('click', this._toggleExpandFilters.bind(this));\n\n $(document).on('user-changed', this._refreshSelector.bind(this));\n $(document).on('done-saving-show-next', this._handleNextUser.bind(this));\n\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.on('change', 'select', this._filterChanged.bind(this));\n\n var userid = $('[data-region=\"grading-navigation-panel\"]').data('first-userid');\n if (userid) {\n this._selectUserById(userid);\n }\n\n str.get_string('changeuser', 'mod_assign').done(function(s) {\n autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);\n }\n ).fail(notification.exception);\n\n $(document).bind(\"start-loading-user\", function() {\n this._isLoading = true;\n }.bind(this));\n $(document).bind(\"finish-loading-user\", function() {\n this._isLoading = false;\n }.bind(this));\n };\n\n /** @property {Boolean} Boolean tracking active ajax requests. */\n GradingNavigation.prototype._isLoading = false;\n\n /** @property {String} Selector for the page region containing the user navigation. */\n GradingNavigation.prototype._regionSelector = null;\n\n /** @property {Array} The list of active filter keys */\n GradingNavigation.prototype._filters = null;\n\n /** @property {Array} The list of users */\n GradingNavigation.prototype._users = null;\n\n /** @property {JQuery} JQuery node for the page region containing the user navigation. */\n GradingNavigation.prototype._region = null;\n\n /** @property {String} Last active filters */\n GradingNavigation.prototype._lastFilters = '';\n\n /**\n * Load the list of all users for this assignment.\n *\n * @private\n * @method _loadAllUsers\n * @return {Boolean} True if the user list was fetched.\n */\n GradingNavigation.prototype._loadAllUsers = function() {\n var select = this._region.find('[data-action=change-user]');\n var assignmentid = select.attr('data-assignmentid');\n var groupid = select.attr('data-groupid');\n\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filter = filterPanel.find('select[name=\"filter\"]').val();\n var workflowFilter = filterPanel.find('select[name=\"workflowfilter\"]');\n if (workflowFilter) {\n filter += ',' + workflowFilter.val();\n }\n var markerFilter = filterPanel.find('select[name=\"markerfilter\"]');\n if (markerFilter) {\n filter += ',' + markerFilter.val();\n }\n\n if (this._lastFilters == filter) {\n return false;\n }\n this._lastFilters = filter;\n\n ajax.call([{\n methodname: 'mod_assign_list_participants',\n args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},\n done: this._usersLoaded.bind(this),\n fail: notification.exception\n }]);\n return true;\n };\n\n /**\n * Call back to rebuild the user selector and x of y info when the user list is updated.\n *\n * @private\n * @method _usersLoaded\n * @param {Array} users\n */\n GradingNavigation.prototype._usersLoaded = function(users) {\n this._firstLoadUsers = false;\n this._filteredUsers = this._users = users;\n if (this._users.length) {\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.find('select[name=\"filter\"]').trigger('change');\n } else {\n this._selectNoUser();\n }\n this._triggerNextUserEvent();\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _checkClickOutsideConfigureFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {\n var configPanel = this._region.find('[data-region=\"configure-filters\"]');\n\n if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n }\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _updateFilterPreference\n * @param {Number} userId The current user id.\n * @param {Array} filterList The list of current filter values.\n * @param {Array} preferenceNames The names of the preferences to update\n * @return {Promise} Resolved when all the preferences are updated.\n */\n GradingNavigation.prototype._updateFilterPreferences = function(userId, filterList, preferenceNames) {\n var preferences = [],\n i = 0;\n\n if (filterList.length == 0 || this._firstLoadUsers) {\n // Nothing to update.\n var deferred = $.Deferred();\n deferred.resolve();\n return deferred;\n }\n // General filter.\n // Set the user preferences to the current filters.\n for (i = 0; i < filterList.length; i++) {\n var newValue = filterList[i];\n if (newValue == 'none') {\n newValue = '';\n }\n\n preferences.push({\n userid: userId,\n name: preferenceNames[i],\n value: newValue\n });\n }\n\n return ajax.call([{\n methodname: 'core_user_set_user_preferences',\n args: {\n preferences: preferences\n }\n }])[0];\n };\n /**\n * Turn a filter on or off.\n *\n * @private\n * @method _filterChanged\n */\n GradingNavigation.prototype._filterChanged = function() {\n // There are 3 types of filter right now.\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filters = filterPanel.find('select');\n var preferenceNames = [];\n\n this._filters = [];\n filters.each(function(idx, ele) {\n var element = $(ele);\n this._filters.push(element.val());\n preferenceNames.push('assign_' + element.prop('name'));\n }.bind(this));\n\n // Update the active filter string.\n var filterlist = [];\n filterPanel.find('option:checked').each(function(idx, ele) {\n filterlist[filterlist.length] = $(ele).text();\n });\n if (filterlist.length) {\n this._region.find('[data-region=\"user-filters\"] span').text(filterlist.join(', '));\n } else {\n str.get_string('nofilters', 'mod_assign').done(function(s) {\n this._region.find('[data-region=\"user-filters\"] span').text(s);\n }.bind(this)).fail(notification.exception);\n }\n\n var select = this._region.find('[data-action=change-user]');\n var currentUserID = select.data('currentuserid');\n this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).done(function() {\n // Reload the list of users to apply the new filters.\n if (!this._loadAllUsers()) {\n var userid = parseInt(select.attr('data-selected'));\n var foundIndex = 0;\n // Search the returned users for the current selection.\n $.each(this._filteredUsers, function(index, user) {\n if (userid == user.id) {\n foundIndex = index;\n }\n });\n\n if (this._filteredUsers.length) {\n this._selectUserById(this._filteredUsers[foundIndex].id);\n } else {\n this._selectNoUser();\n }\n\n }\n }.bind(this)).fail(notification.exception);\n this._refreshCount();\n };\n\n /**\n * Select no users, because no users match the filters.\n *\n * @private\n * @method _selectNoUser\n */\n GradingNavigation.prototype._selectNoUser = function() {\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', -1);\n });\n });\n } else {\n $(document).trigger('user-changed', -1);\n }\n };\n\n /**\n * Select the specified user by id.\n *\n * @private\n * @method _selectUserById\n * @param {Number} userid\n */\n GradingNavigation.prototype._selectUserById = function(userid) {\n var select = this._region.find('[data-action=change-user]');\n var useridnumber = parseInt(userid, 10);\n\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', useridnumber);\n });\n });\n } else {\n select.attr('data-selected', userid);\n\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n /**\n * Expand or collapse the filter config panel.\n *\n * @private\n * @method _toggleExpandFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._toggleExpandFilters = function(event) {\n event.preventDefault();\n var toggleLink = $(event.target).closest('[data-region=\"user-filters\"]');\n var expanded = toggleLink.attr('aria-expanded') == 'true';\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n if (expanded) {\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n } else {\n configPanel.css('display', 'inline-block');\n configPanel.attr('aria-hidden', 'false');\n toggleLink.attr('aria-expanded', 'true');\n event.stopPropagation();\n $(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));\n }\n };\n\n /**\n * Change to the previous user in the grading list.\n *\n * @private\n * @method _handlePreviousUser\n * @param {Event} e\n */\n GradingNavigation.prototype._handlePreviousUser = function(e) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex - 1);\n if (newIndex < 0) {\n newIndex = count - 1;\n }\n\n if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Change to the next user in the grading list.\n *\n * @param {Event} e\n * @param {Boolean} saved Has the form already been saved? Skips checking for changes if true.\n */\n GradingNavigation.prototype._handleNextUser = function(e, saved) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex + 1) % count;\n\n if (saved && count) {\n // If we've already saved the grade, skip checking if we've made any changes.\n var userid = this._filteredUsers[newIndex].id;\n var useridnumber = parseInt(userid, 10);\n select.attr('data-selected', userid);\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n } else if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Set count string. This method only sets the value for the last time it was ever called to deal\n * with promises that return in a non-predictable order.\n *\n * @private\n * @method _setCountString\n * @param {Number} x\n * @param {Number} y\n */\n GradingNavigation.prototype._setCountString = function(x, y) {\n var updateNumber = 0;\n this._lastXofYUpdate++;\n updateNumber = this._lastXofYUpdate;\n\n var param = {x: x, y: y};\n str.get_string('xofy', 'mod_assign', param).done(function(s) {\n if (updateNumber == this._lastXofYUpdate) {\n this._region.find('[data-region=\"user-count-summary\"]').text(s);\n }\n }.bind(this)).fail(notification.exception);\n };\n\n /**\n * Rebuild the x of y string.\n *\n * @private\n * @method _refreshCount\n */\n GradingNavigation.prototype._refreshCount = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n if (isNaN(userid) || userid <= 0) {\n this._region.find('[data-region=\"user-count\"]').hide();\n } else {\n this._region.find('[data-region=\"user-count\"]').show();\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == userid) {\n currentIndex = i;\n break;\n }\n }\n var count = this._filteredUsers.length;\n if (count) {\n currentIndex += 1;\n }\n this._setCountString(currentIndex, count);\n // Update window URL\n if (currentIndex > 0) {\n var url = new URL(window.location);\n if (parseInt(url.searchParams.get('blindid')) > 0) {\n var newid = this._filteredUsers[currentIndex - 1].recordid;\n url.searchParams.set('blindid', newid);\n } else {\n url.searchParams.set('userid', userid);\n }\n // We do this so a browser refresh will return to the same user.\n window.history.replaceState({}, \"\", url);\n }\n }\n };\n\n /**\n * Respond to a user-changed event by updating the selector.\n *\n * @private\n * @method _refreshSelector\n * @param {Event} event\n * @param {String} userid\n */\n GradingNavigation.prototype._refreshSelector = function(event, userid) {\n var select = this._region.find('[data-action=change-user]');\n userid = parseInt(userid, 10);\n\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n }\n this._refreshCount();\n };\n\n /**\n * Trigger the next user event depending on the number of filtered users\n *\n * @private\n * @method _triggerNextUserEvent\n */\n GradingNavigation.prototype._triggerNextUserEvent = function() {\n if (this._filteredUsers.length > 1) {\n $(document).trigger('next-user', {nextUserId: null, nextUser: true});\n } else {\n $(document).trigger('next-user', {nextUser: false});\n }\n };\n\n /**\n * Change to a different user in the grading list.\n *\n * @private\n * @method _handleChangeUser\n */\n GradingNavigation.prototype._handleChangeUser = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = parseInt(select.val(), 10);\n\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', userid);\n });\n });\n } else {\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n return GradingNavigation;\n});\n"],"names":["define","$","notification","str","autocomplete","ajax","checker","GradingNavigation","selector","_regionSelector","_region","_filters","_users","_filteredUsers","_lastXofYUpdate","_firstLoadUsers","_loadAllUsers","find","on","this","_handlePreviousUser","bind","_handleNextUser","_handleChangeUser","_toggleExpandFilters","document","_refreshSelector","toggleLink","getElementById","attr","_filterChanged","userid","data","_selectUserById","get_string","done","s","enhance","fail","exception","_isLoading","prototype","_lastFilters","select","assignmentid","groupid","filterPanel","filter","val","workflowFilter","markerFilter","call","methodname","args","assignid","onlyids","tablesort","_usersLoaded","users","length","trigger","_selectNoUser","_triggerNextUserEvent","_checkClickOutsideConfigureFilters","event","configPanel","is","target","has","hide","unbind","_updateFilterPreferences","userId","filterList","preferenceNames","preferences","i","deferred","Deferred","resolve","newValue","push","name","value","filters","each","idx","ele","element","prop","filterlist","text","join","currentUserID","parseInt","foundIndex","index","user","id","_refreshCount","checkFormForChanges","get_strings","key","component","strs","confirm","useridnumber","isNaN","preventDefault","closest","expanded","css","stopPropagation","e","currentUserId","currentIndex","count","newIndex","saved","_setCountString","x","y","updateNumber","param","show","url","URL","window","location","searchParams","get","newid","recordid","set","history","replaceState","nextUserId","nextUser"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"grading_navigation.min.js","sources":["../src/grading_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to handle changing users via the user selector in the header.\n *\n * @module mod_assign/grading_navigation\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',\n 'core/ajax', 'mod_assign/grading_form_change_checker'],\n function($, notification, str, autocomplete, ajax, checker) {\n\n /**\n * GradingNavigation class.\n *\n * @class mod_assign/grading_navigation\n * @param {String} selector The selector for the page region containing the user navigation.\n */\n var GradingNavigation = function(selector) {\n this._regionSelector = selector;\n this._region = $(selector);\n this._filters = [];\n this._users = [];\n this._filteredUsers = [];\n this._lastXofYUpdate = 0;\n this._firstLoadUsers = true;\n\n let url = new URL(window.location);\n if (parseInt(url.searchParams.get('treset')) > 0) {\n // Remove 'treset' url parameter to make sure that\n // table preferences won't be reset on page refresh.\n url.searchParams.delete('treset');\n window.history.replaceState({}, \"\", url);\n }\n\n // Get the current user list from a webservice.\n this._loadAllUsers();\n\n // We do not allow navigation while ajax requests are pending.\n // Attach listeners to the select and arrow buttons.\n\n this._region.find('[data-action=\"previous-user\"]').on('click', this._handlePreviousUser.bind(this));\n this._region.find('[data-action=\"next-user\"]').on('click', this._handleNextUser.bind(this));\n this._region.find('[data-action=\"change-user\"]').on('change', this._handleChangeUser.bind(this));\n this._region.find('[data-region=\"user-filters\"]').on('click', this._toggleExpandFilters.bind(this));\n this._region.find('[data-region=\"user-resettable\"]').on('click', this._toggleResetTable.bind());\n\n $(document).on('user-changed', this._refreshSelector.bind(this));\n $(document).on('done-saving-show-next', this._handleNextUser.bind(this));\n\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.on('change', 'select', this._filterChanged.bind(this));\n\n var userid = $('[data-region=\"grading-navigation-panel\"]').data('first-userid');\n if (userid) {\n this._selectUserById(userid);\n }\n\n str.get_string('changeuser', 'mod_assign').done(function(s) {\n autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);\n }\n ).fail(notification.exception);\n\n $(document).bind(\"start-loading-user\", function() {\n this._isLoading = true;\n }.bind(this));\n $(document).bind(\"finish-loading-user\", function() {\n this._isLoading = false;\n }.bind(this));\n };\n\n /** @property {Boolean} Boolean tracking active ajax requests. */\n GradingNavigation.prototype._isLoading = false;\n\n /** @property {String} Selector for the page region containing the user navigation. */\n GradingNavigation.prototype._regionSelector = null;\n\n /** @property {Array} The list of active filter keys */\n GradingNavigation.prototype._filters = null;\n\n /** @property {Array} The list of users */\n GradingNavigation.prototype._users = null;\n\n /** @property {JQuery} JQuery node for the page region containing the user navigation. */\n GradingNavigation.prototype._region = null;\n\n /** @property {String} Last active filters */\n GradingNavigation.prototype._lastFilters = '';\n\n /**\n * Load the list of all users for this assignment.\n *\n * @private\n * @method _loadAllUsers\n * @return {Boolean} True if the user list was fetched.\n */\n GradingNavigation.prototype._loadAllUsers = function() {\n var select = this._region.find('[data-action=change-user]');\n var assignmentid = select.attr('data-assignmentid');\n var groupid = select.attr('data-groupid');\n\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filter = filterPanel.find('select[name=\"filter\"]').val();\n var workflowFilter = filterPanel.find('select[name=\"workflowfilter\"]');\n if (workflowFilter) {\n filter += ',' + workflowFilter.val();\n }\n var markerFilter = filterPanel.find('select[name=\"markerfilter\"]');\n if (markerFilter) {\n filter += ',' + markerFilter.val();\n }\n\n if (this._lastFilters == filter) {\n return false;\n }\n this._lastFilters = filter;\n\n ajax.call([{\n methodname: 'mod_assign_list_participants',\n args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},\n done: this._usersLoaded.bind(this),\n fail: notification.exception\n }]);\n return true;\n };\n\n /**\n * Call back to rebuild the user selector and x of y info when the user list is updated.\n *\n * @private\n * @method _usersLoaded\n * @param {Array} users\n */\n GradingNavigation.prototype._usersLoaded = function(users) {\n this._firstLoadUsers = false;\n this._filteredUsers = this._users = users;\n if (this._users.length) {\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.find('select[name=\"filter\"]').trigger('change');\n } else {\n this._selectNoUser();\n }\n this._triggerNextUserEvent();\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _checkClickOutsideConfigureFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {\n var configPanel = this._region.find('[data-region=\"configure-filters\"]');\n\n if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n }\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _updateFilterPreference\n * @param {Number} userId The current user id.\n * @param {Array} filterList The list of current filter values.\n * @param {Array} preferenceNames The names of the preferences to update\n * @return {Promise} Resolved when all the preferences are updated.\n */\n GradingNavigation.prototype._updateFilterPreferences = function(userId, filterList, preferenceNames) {\n var preferences = [],\n i = 0;\n\n if (filterList.length == 0 || this._firstLoadUsers) {\n // Nothing to update.\n var deferred = $.Deferred();\n deferred.resolve();\n return deferred;\n }\n // General filter.\n // Set the user preferences to the current filters.\n for (i = 0; i < filterList.length; i++) {\n var newValue = filterList[i];\n if (newValue == 'none') {\n newValue = '';\n }\n\n preferences.push({\n userid: userId,\n name: preferenceNames[i],\n value: newValue\n });\n }\n\n return ajax.call([{\n methodname: 'core_user_set_user_preferences',\n args: {\n preferences: preferences\n }\n }])[0];\n };\n /**\n * Turn a filter on or off.\n *\n * @private\n * @method _filterChanged\n */\n GradingNavigation.prototype._filterChanged = function() {\n // There are 3 types of filter right now.\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filters = filterPanel.find('select');\n var preferenceNames = [];\n\n this._filters = [];\n filters.each(function(idx, ele) {\n var element = $(ele);\n this._filters.push(element.val());\n preferenceNames.push('assign_' + element.prop('name'));\n }.bind(this));\n\n // Update the active filter string.\n var filterlist = [];\n filterPanel.find('option:checked').each(function(idx, ele) {\n filterlist[filterlist.length] = $(ele).text();\n });\n if (filterlist.length) {\n this._region.find('[data-region=\"user-filters\"] span').text(filterlist.join(', '));\n } else {\n str.get_string('nofilters', 'mod_assign').done(function(s) {\n this._region.find('[data-region=\"user-filters\"] span').text(s);\n }.bind(this)).fail(notification.exception);\n }\n\n var select = this._region.find('[data-action=change-user]');\n var currentUserID = select.data('currentuserid');\n this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).done(function() {\n // Reload the list of users to apply the new filters.\n if (!this._loadAllUsers()) {\n var userid = parseInt(select.attr('data-selected'));\n let foundIndex = null;\n // Search the returned users for the current selection.\n $.each(this._filteredUsers, function(index, user) {\n if (userid == user.id) {\n foundIndex = index;\n }\n });\n\n if (this._filteredUsers.length && foundIndex !== null) {\n this._selectUserById(this._filteredUsers[foundIndex].id);\n } else {\n this._selectNoUser();\n }\n\n }\n }.bind(this)).fail(notification.exception);\n this._refreshCount();\n };\n\n /**\n * Select no users, because no users match the filters.\n *\n * @private\n * @method _selectNoUser\n */\n GradingNavigation.prototype._selectNoUser = function() {\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', -1);\n });\n });\n } else {\n $(document).trigger('user-changed', -1);\n }\n };\n\n /**\n * Select the specified user by id.\n *\n * @private\n * @method _selectUserById\n * @param {Number} userid\n */\n GradingNavigation.prototype._selectUserById = function(userid) {\n var select = this._region.find('[data-action=change-user]');\n var useridnumber = parseInt(userid, 10);\n\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', useridnumber);\n });\n });\n } else {\n select.attr('data-selected', userid);\n\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n /**\n * Expand or collapse the filter config panel.\n *\n * @private\n * @method _toggleExpandFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._toggleExpandFilters = function(event) {\n event.preventDefault();\n var toggleLink = $(event.target).closest('[data-region=\"user-filters\"]');\n var expanded = toggleLink.attr('aria-expanded') == 'true';\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n if (expanded) {\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n } else {\n configPanel.css('display', 'inline-block');\n configPanel.attr('aria-hidden', 'false');\n toggleLink.attr('aria-expanded', 'true');\n event.stopPropagation();\n $(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));\n }\n };\n\n /**\n * Reset table preferences.\n *\n * @private\n * @method _toggleResetTable\n */\n GradingNavigation.prototype._toggleResetTable = function() {\n let url = new URL(window.location);\n url.searchParams.set('treset', '1');\n window.location.href = url;\n };\n\n /**\n * Change to the previous user in the grading list.\n *\n * @private\n * @method _handlePreviousUser\n * @param {Event} e\n */\n GradingNavigation.prototype._handlePreviousUser = function(e) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex - 1);\n if (newIndex < 0) {\n newIndex = count - 1;\n }\n\n if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Change to the next user in the grading list.\n *\n * @param {Event} e\n * @param {Boolean} saved Has the form already been saved? Skips checking for changes if true.\n */\n GradingNavigation.prototype._handleNextUser = function(e, saved) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex + 1) % count;\n\n if (saved && count) {\n // If we've already saved the grade, skip checking if we've made any changes.\n var userid = this._filteredUsers[newIndex].id;\n var useridnumber = parseInt(userid, 10);\n select.attr('data-selected', userid);\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n } else if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Set count string. This method only sets the value for the last time it was ever called to deal\n * with promises that return in a non-predictable order.\n *\n * @private\n * @method _setCountString\n * @param {Number} x\n * @param {Number} y\n */\n GradingNavigation.prototype._setCountString = function(x, y) {\n var updateNumber = 0;\n this._lastXofYUpdate++;\n updateNumber = this._lastXofYUpdate;\n\n var param = {x: x, y: y};\n str.get_string('xofy', 'mod_assign', param).done(function(s) {\n if (updateNumber == this._lastXofYUpdate) {\n this._region.find('[data-region=\"user-count-summary\"]').text(s);\n }\n }.bind(this)).fail(notification.exception);\n };\n\n /**\n * Rebuild the x of y string.\n *\n * @private\n * @method _refreshCount\n */\n GradingNavigation.prototype._refreshCount = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n if (isNaN(userid) || userid <= 0) {\n this._region.find('[data-region=\"user-count\"]').hide();\n } else {\n this._region.find('[data-region=\"user-count\"]').show();\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == userid) {\n currentIndex = i;\n break;\n }\n }\n var count = this._filteredUsers.length;\n if (count) {\n currentIndex += 1;\n }\n this._setCountString(currentIndex, count);\n // Update window URL\n if (currentIndex > 0) {\n var url = new URL(window.location);\n if (parseInt(url.searchParams.get('blindid')) > 0) {\n var newid = this._filteredUsers[currentIndex - 1].recordid;\n url.searchParams.set('blindid', newid);\n } else {\n url.searchParams.set('userid', userid);\n }\n // We do this so a browser refresh will return to the same user.\n window.history.replaceState({}, \"\", url);\n }\n }\n };\n\n /**\n * Respond to a user-changed event by updating the selector.\n *\n * @private\n * @method _refreshSelector\n * @param {Event} event\n * @param {String} userid\n */\n GradingNavigation.prototype._refreshSelector = function(event, userid) {\n var select = this._region.find('[data-action=change-user]');\n userid = parseInt(userid, 10);\n\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n }\n this._refreshCount();\n };\n\n /**\n * Trigger the next user event depending on the number of filtered users\n *\n * @private\n * @method _triggerNextUserEvent\n */\n GradingNavigation.prototype._triggerNextUserEvent = function() {\n if (this._filteredUsers.length > 1) {\n $(document).trigger('next-user', {nextUserId: null, nextUser: true});\n } else {\n $(document).trigger('next-user', {nextUser: false});\n }\n };\n\n /**\n * Change to a different user in the grading list.\n *\n * @private\n * @method _handleChangeUser\n */\n GradingNavigation.prototype._handleChangeUser = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = parseInt(select.val(), 10);\n\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', userid);\n });\n });\n } else {\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n return GradingNavigation;\n});\n"],"names":["define","$","notification","str","autocomplete","ajax","checker","GradingNavigation","selector","_regionSelector","_region","_filters","_users","_filteredUsers","_lastXofYUpdate","_firstLoadUsers","url","URL","window","location","parseInt","searchParams","get","delete","history","replaceState","_loadAllUsers","find","on","this","_handlePreviousUser","bind","_handleNextUser","_handleChangeUser","_toggleExpandFilters","_toggleResetTable","document","_refreshSelector","toggleLink","getElementById","attr","_filterChanged","userid","data","_selectUserById","get_string","done","s","enhance","fail","exception","_isLoading","prototype","_lastFilters","select","assignmentid","groupid","filterPanel","filter","val","workflowFilter","markerFilter","call","methodname","args","assignid","onlyids","tablesort","_usersLoaded","users","length","trigger","_selectNoUser","_triggerNextUserEvent","_checkClickOutsideConfigureFilters","event","configPanel","is","target","has","hide","unbind","_updateFilterPreferences","userId","filterList","preferenceNames","preferences","i","deferred","Deferred","resolve","newValue","push","name","value","filters","each","idx","ele","element","prop","filterlist","text","join","currentUserID","foundIndex","index","user","id","_refreshCount","checkFormForChanges","get_strings","key","component","strs","confirm","useridnumber","isNaN","preventDefault","closest","expanded","css","stopPropagation","set","href","e","currentUserId","currentIndex","count","newIndex","saved","_setCountString","x","y","updateNumber","param","show","newid","recordid","nextUserId","nextUser"],"mappings":"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"} \ No newline at end of file diff --git a/mod/assign/amd/src/grading_navigation.js b/mod/assign/amd/src/grading_navigation.js index 3318b075a21..fd3a09ced8e 100644 --- a/mod/assign/amd/src/grading_navigation.js +++ b/mod/assign/amd/src/grading_navigation.js @@ -40,6 +40,14 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', this._lastXofYUpdate = 0; this._firstLoadUsers = true; + let url = new URL(window.location); + if (parseInt(url.searchParams.get('treset')) > 0) { + // Remove 'treset' url parameter to make sure that + // table preferences won't be reset on page refresh. + url.searchParams.delete('treset'); + window.history.replaceState({}, "", url); + } + // Get the current user list from a webservice. this._loadAllUsers(); @@ -50,6 +58,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', this._region.find('[data-action="next-user"]').on('click', this._handleNextUser.bind(this)); this._region.find('[data-action="change-user"]').on('change', this._handleChangeUser.bind(this)); this._region.find('[data-region="user-filters"]').on('click', this._toggleExpandFilters.bind(this)); + this._region.find('[data-region="user-resettable"]').on('click', this._toggleResetTable.bind()); $(document).on('user-changed', this._refreshSelector.bind(this)); $(document).on('done-saving-show-next', this._handleNextUser.bind(this)); @@ -255,7 +264,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', // Reload the list of users to apply the new filters. if (!this._loadAllUsers()) { var userid = parseInt(select.attr('data-selected')); - var foundIndex = 0; + let foundIndex = null; // Search the returned users for the current selection. $.each(this._filteredUsers, function(index, user) { if (userid == user.id) { @@ -263,7 +272,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', } }); - if (this._filteredUsers.length) { + if (this._filteredUsers.length && foundIndex !== null) { this._selectUserById(this._filteredUsers[foundIndex].id); } else { this._selectNoUser(); @@ -365,6 +374,18 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', } }; + /** + * Reset table preferences. + * + * @private + * @method _toggleResetTable + */ + GradingNavigation.prototype._toggleResetTable = function() { + let url = new URL(window.location); + url.searchParams.set('treset', '1'); + window.location.href = url; + }; + /** * Change to the previous user in the grading list. * diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index d0968f5a497..3eb92287437 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -2846,6 +2846,12 @@ class mod_assign_external extends \mod_assign\external\external_api { throw new moodle_exception('usernotincourse'); } + $filtered = $assign->is_userid_filtered($userid); + if (!$filtered) { + // User is filtered out by user filters or table preferences. + throw new moodle_exception('userisfilteredout'); + } + $return = array( 'id' => $participant->id, 'fullname' => $participant->fullname, diff --git a/mod/assign/lib.php b/mod/assign/lib.php index abd2c77f4f9..2c2413416a6 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -1438,6 +1438,14 @@ function mod_assign_output_fragment_gradingpanel($args) { $assign = new assign($context, null, null); $userid = clean_param($args['userid'], PARAM_INT); + + $participant = $assign->get_participant($userid); + $isfiltered = $assign->is_userid_filtered($userid); + if (!$participant || !$isfiltered) { + // User is not enrolled or filtered out by filters and table preferences. + return ''; + } + $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT); $formdata = array(); if (!empty($args['jsonformdata'])) { diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 47e0a9a0232..7a28e308c0c 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -2637,6 +2637,17 @@ class assign { return $useridlist; } + /** + * Is user id filtered by user filters and table preferences. + * + * @param int $userid User id that needs to be checked. + * @return bool + */ + public function is_userid_filtered($userid) { + $users = $this->get_grading_userid_list(); + return in_array($userid, $users); + } + /** * Finds all assignment notifications that have yet to be mailed out, and mails them. * @@ -4778,6 +4789,10 @@ class assign { $userid = $this->get_user_id_for_uniqueid($blindid); } + // Instantiate table object to apply table preferences. + $gradingtable = new assign_grading_table($this, 10, '', 0, false); + $gradingtable->setup(); + $currentgroup = groups_get_activity_group($this->get_course_module(), true); $framegrader = new grading_app($userid, $currentgroup, $this); diff --git a/mod/assign/templates/grading_navigation_user_selector.mustache b/mod/assign/templates/grading_navigation_user_selector.mustache index 656441768ee..aa3a1a11148 100644 --- a/mod/assign/templates/grading_navigation_user_selector.mustache +++ b/mod/assign/templates/grading_navigation_user_selector.mustache @@ -29,6 +29,9 @@ * see mod/assign/classes/output/grading_app.php This template uses ajax functionality, so it cannot be shown in the template library. + + Example context (json): + { } }} {{{larrow}}} @@ -55,6 +58,9 @@ {{#pix}}i/filter{{/pix}} + + {{#str}}resettable{{/str}} +
diff --git a/mod/assign/tests/behat/assign_table_preferences.feature b/mod/assign/tests/behat/assign_table_preferences.feature new file mode 100644 index 00000000000..b36a78815d8 --- /dev/null +++ b/mod/assign/tests/behat/assign_table_preferences.feature @@ -0,0 +1,57 @@ +@mod @mod_assign +Feature: In an assignment, teachers can use table preferences. + In order to improve grading process + As a teacher + I need to be able to filter students by first and last name. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | One | student1@example.com | + | student2 | Student | Two | student2@example.com | + | teacher1 | Darrell | Teacher1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | enablecompletion | showcompletionconditions | + | Course 1 | C1 | 1 | 1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | teacher1 | C1 | editingteacher | + And the following "activity" exists: + | activity | assign | + | course | C1 | + | name | Test assignment | + | assignsubmission_onlinetext_enabled | 1 | + And I log out + And I log in as "student1" + And I am on the "Test assignment" Activity page + And I press "Add submission" + And I set the following fields to these values: + | Online text | This is a submission for Student One | + And I press "Save changes" + And I press "Submit assignment" + And I press "Continue" + And I log out + And I log in as "student2" + And I am on the "Test assignment" Activity page + And I press "Add submission" + And I set the following fields to these values: + | Online text | This is a submission for Student Two | + And I press "Save changes" + And I press "Submit assignment" + And I press "Continue" + + @javascript + Scenario: As a teacher I can filter student submissions on the View all submission page + When I log in as "teacher1" + And I am on the "Test assignment" Activity page + And I follow "View all submissions" + And I click on "T" "link" in the ".lastinitial" "css_element" + And I click on "Grade" "link" in the "Student Two" "table_row" + And I should see "This is a submission for Student Two" + And I should see "1 of 1" + And I follow "Reset table preferences" + Then I should see "This is a submission for Student Two" + And I should see "2 of 2" + And I log out diff --git a/mod/assign/tests/behat/filter_drafts.feature b/mod/assign/tests/behat/filter_drafts.feature index 53a111d4e19..54b1597ca95 100644 --- a/mod/assign/tests/behat/filter_drafts.feature +++ b/mod/assign/tests/behat/filter_drafts.feature @@ -56,6 +56,9 @@ Feature: In an assignment, teachers can filter displayed submissions and see dra When I click on "[data-region=user-filters]" "css_element" And I set the field "filter" to "Draft" Then I should see "1 of 1" + And I should see "No users selected" + And I click on "[data-region=user-selector]" "css_element" + And I type "Student" And I should see "Student 2" And I should not see "Student 1" And I should not see "Student 3" diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index 49d4c218e67..e91b5291cc5 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -4528,4 +4528,71 @@ Anchor link 2:Link text return array($assign, $instance, $student); } + + /** + * Test user filtering by First name, Last name and Submission status. + * + * @covers \assign::is_userid_filtered + */ + public function test_is_userid_filtered() { + $this->resetAfterTest(); + + // Generate data and simulate student submissions. + $course = $this->getDataGenerator()->create_course(); + $params1 = ['firstname' => 'Valentin', 'lastname' => 'Ivanov']; + $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params1); + $params2 = ['firstname' => 'Nikolay', 'lastname' => 'Petrov']; + $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params2); + $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); + $this->setUser($student1); + $submission = $assign->get_user_submission($student1->id, true); + $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; + $assign->testable_update_submission($submission, $student1->id, true, false); + $this->setUser($student2); + $submission = $assign->get_user_submission($student2->id, true); + $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT; + $assign->testable_update_submission($submission, $student2->id, true, false); + $this->setUser($teacher); + + // By default, both users should match filters. + $this->AssertTrue($assign->is_userid_filtered($student1->id)); + $this->AssertTrue($assign->is_userid_filtered($student2->id)); + + // Filter by First name starting with V. + $_GET['tifirst'] = 'V'; + $this->AssertTrue($assign->is_userid_filtered($student1->id)); + $this->AssertFalse($assign->is_userid_filtered($student2->id)); + + // Add Last name to filter out both users. + $_GET['tilast'] = 'G'; + $this->AssertFalse($assign->is_userid_filtered($student1->id)); + $this->AssertFalse($assign->is_userid_filtered($student2->id)); + + // Unsetting variables doesn't change behaviour because filters are stored in user preferences. + unset($_GET['tifirst']); + unset($_GET['tilast']); + $this->AssertFalse($assign->is_userid_filtered($student1->id)); + $this->AssertFalse($assign->is_userid_filtered($student2->id)); + + // Reset table preferences. + $_GET['treset'] = '1'; + $this->AssertTrue($assign->is_userid_filtered($student1->id)); + $this->AssertTrue($assign->is_userid_filtered($student2->id)); + + // Display users with submitted submissions only. + set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_SUBMITTED); + $this->AssertTrue($assign->is_userid_filtered($student1->id)); + $this->AssertFalse($assign->is_userid_filtered($student2->id)); + + // Display users with drafts. + set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_DRAFT); + $this->AssertFalse($assign->is_userid_filtered($student1->id)); + $this->AssertTrue($assign->is_userid_filtered($student2->id)); + + // Reset the filter. + set_user_preference('assign_filter', ''); + $this->AssertTrue($assign->is_userid_filtered($student1->id)); + $this->AssertTrue($assign->is_userid_filtered($student2->id)); + } } diff --git a/mod/assign/upgrade.txt b/mod/assign/upgrade.txt index d5b4cfbb962..94d0a1aa77e 100644 --- a/mod/assign/upgrade.txt +++ b/mod/assign/upgrade.txt @@ -1,4 +1,8 @@ This files describes API changes in the assign code. +=== 4.1 === +* New method \assign::is_userid_filtered() has been implemented. It returns false if user id is filtered out by either + user preferences for grading table or submission status filter. Otherwise, returns true. + === 4.0 === * The method \assign::grading_disabled() now has optional $gradinginfo parameter to improve performance * Renderer (renderer.php) has been moved from mod root to classes/output/ to be more PSR compliant.