MDL-66381 mod_forum: User search and dropdown
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
define ("mod_forum/local/grades/local/grader/user_picker/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={regions:{userRegion:"[data-region=\"user_picker/user\"]"},actions:{changeUser:"[data-action=\"change-user\"]"}};return a.default});
|
||||
define ("mod_forum/local/grades/local/grader/user_picker/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={regions:{userRegion:"[data-region=\"user_picker/user\"]"},actions:{changeUser:"[data-action=\"change-user\"]",selectUser:"[data-action=\"select-user\"]",searchUserBox:"[data-action=\"search-user-box\"]",searchUserInput:"[data-action=\"search-user-input\"]"}};return a.default});
|
||||
//# sourceMappingURL=selectors.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/local/grades/local/grader/user_picker/selectors.js"],"names":["regions","userRegion","actions","changeUser"],"mappings":"qLAwBe,CACXA,OAAO,CAAE,CACLC,UAAU,CAAE,oCADP,CADE,CAIXC,OAAO,CAAE,CACLC,UAAU,CAAE,+BADP,CAJE,C","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/user_picker/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n regions: {\n userRegion: '[data-region=\"user_picker/user\"]',\n },\n actions: {\n changeUser: '[data-action=\"change-user\"]',\n }\n};\n\n"],"file":"selectors.min.js"}
|
||||
{"version":3,"sources":["../../../../../../src/local/grades/local/grader/user_picker/selectors.js"],"names":["regions","userRegion","actions","changeUser","selectUser","searchUserBox","searchUserInput"],"mappings":"qLAwBe,CACXA,OAAO,CAAE,CACLC,UAAU,CAAE,oCADP,CADE,CAIXC,OAAO,CAAE,CACLC,UAAU,CAAE,+BADP,CAELC,UAAU,CAAE,+BAFP,CAGLC,aAAa,CAAE,mCAHV,CAILC,eAAe,CAAE,qCAJZ,CAJE,C","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/user_picker/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n regions: {\n userRegion: '[data-region=\"user_picker/user\"]',\n },\n actions: {\n changeUser: '[data-action=\"change-user\"]',\n selectUser: '[data-action=\"select-user\"]',\n searchUserBox: '[data-action=\"search-user-box\"]',\n searchUserInput: '[data-action=\"search-user-input\"]',\n }\n};\n\n"],"file":"selectors.min.js"}
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("mod_forum/local/layout/fullscreen",["exports","core/loadingicon"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.createLayout=void 0;var c=function(a){var c=document.createElement("div");a.append(c);var d=document.createElement("div");a.append(d);var f=function(){if(a.requestFullscreen){a.requestFullscreen()}else if(a.msRequestFullscreen){a.msRequestFullscreen()}else if(a.mozRequestFullscreen){a.mozRequestFullscreen()}else if(a.webkitRequestFullscreen){a.webkitRequestFullscreen()}else{a.setTop(0)}},g=function(){if(document.exitRequestFullScreen){if(document.fullScreenElement!==a){return}document.exitRequestFullScreen()}else if(document.msExitFullscreen){if(document.msFullscreenElement!==a){return}document.msExitFullscreen()}else if(document.mozCancelFullScreen){if(document.mozFullScreenElement!==a){return}document.mozCancelFullScreen()}else if(document.webkitExitFullscreen){if(document.webkitFullscreenElement!==a){return}document.webkitExitFullscreen()}},h=function(){var a=d.lastElementChild;while(a){d.removeChild(a);a=d.lastElementChild}};return{close:function close(){g();e();a.remove()},toggleFullscreen:function toggleFullscreen(){if(document.exitRequestFullScreen){if(document.fullScreenElement===a){g()}else{f()}}else if(document.msExitFullscreen){if(document.msFullscreenElement===a){g()}else{f()}}else if(document.mozCancelFullScreen){if(document.mozFullScreenElement===a){g()}else{f()}}else if(document.webkitExitFullscreen){if(document.webkitFullscreenElement===a){g()}else{f()}}},requestFullscreen:f,exitFullscreen:g,getContainer:function getContainer(){return c},setContent:function setContent(a){h();var b=c.lastElementChild;while(b){c.removeChild(b);b=c.lastElementChild}c.append(a)},showLoadingIcon:function showLoadingIcon(){(0,b.addIconToContainer)(d)},hideLoadingIcon:h}},d=function(){document.querySelector("body").classList.add("overflow-hidden")},e=function(){document.querySelector("body").classList.remove("overflow-hidden")};a.createLayout=function getComposedLayout(){var a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{},b=a.fullscreen,e=void 0===b?!0:b,f=a.showLoader,g=void 0===f?!0:f,h=document.createElement("div");document.body.append(h);h.classList.add("layout");h.classList.add("fullscreen");h.setAttribute("aria-role","application");d();var i=c(h);if(g){i.showLoadingIcon()}if(e){i.requestFullscreen()}return i}});
|
||||
define ("mod_forum/local/layout/fullscreen",["exports","core/loadingicon"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.createLayout=void 0;var c=function(a){var c=document.createElement("div");a.append(c);var d=document.createElement("div");a.append(d);var f=function(){if(a.requestFullscreen){a.requestFullscreen()}else if(a.msRequestFullscreen){a.msRequestFullscreen()}else if(a.mozRequestFullscreen){a.mozRequestFullscreen()}else if(a.webkitRequestFullscreen){a.webkitRequestFullscreen()}else{a.setTop(0)}},g=function(){if(document.exitRequestFullScreen){if(document.fullScreenElement!==a){return}document.exitRequestFullScreen()}else if(document.msExitFullscreen){if(document.msFullscreenElement!==a){return}document.msExitFullscreen()}else if(document.mozCancelFullScreen){if(document.mozFullScreenElement!==a){return}document.mozCancelFullScreen()}else if(document.webkitExitFullscreen){if(document.webkitFullscreenElement!==a){return}document.webkitExitFullscreen()}},h=function(){var a=d.lastElementChild;while(a){d.removeChild(a);a=d.lastElementChild}};return{close:function close(){g();e();a.remove()},toggleFullscreen:function toggleFullscreen(){if(document.exitRequestFullScreen){if(document.fullScreenElement===a){g()}else{f()}}else if(document.msExitFullscreen){if(document.msFullscreenElement===a){g()}else{f()}}else if(document.mozCancelFullScreen){if(document.mozFullScreenElement===a){g()}else{f()}}else if(document.webkitExitFullscreen){if(document.webkitFullscreenElement===a){g()}else{f()}}},requestFullscreen:f,exitFullscreen:g,getContainer:function getContainer(){return c},setContent:function setContent(a){h();var b=c.lastElementChild;while(b){c.removeChild(b);b=c.lastElementChild}c.append(a)},showLoadingIcon:function showLoadingIcon(){(0,b.addIconToContainer)(d)},hideLoadingIcon:h}},d=function(){document.querySelector("body").classList.add("overflow-hidden")},e=function(){document.querySelector("body").classList.remove("overflow-hidden")};a.createLayout=function getComposedLayout(){var a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{},b=a.fullscreen,e=void 0===b?!0:b,f=a.showLoader,g=void 0===f?!1:f,h=document.createElement("div");document.body.append(h);h.classList.add("layout");h.classList.add("fullscreen");h.setAttribute("aria-role","application");d();var i=c(h);if(g){i.showLoadingIcon()}if(e){i.requestFullscreen()}return i}});
|
||||
//# sourceMappingURL=fullscreen.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -29,6 +29,7 @@ import getGradingPanelFunctions from './local/grader/gradingpanel';
|
||||
import {add as addToast} from 'core/toast';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';
|
||||
import {addIconToContainerWithPromise} from 'core/loadingicon';
|
||||
|
||||
const templateNames = {
|
||||
grader: {
|
||||
@@ -72,6 +73,7 @@ const fetchContentFromRender = (html, js) => {
|
||||
*/
|
||||
const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {
|
||||
return async(user) => {
|
||||
const spinner = addIconToContainerWithPromise(root);
|
||||
const [
|
||||
[html, js],
|
||||
userGrade,
|
||||
@@ -86,6 +88,7 @@ const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser)
|
||||
gradingPanelJS
|
||||
] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);
|
||||
Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanel), gradingPanelHtml, gradingPanelJS);
|
||||
spinner.resolve();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
import Templates from 'core/templates';
|
||||
import Selectors from './user_picker/selectors';
|
||||
import {addIconToContainerWithPromise} from 'core/loadingicon';
|
||||
|
||||
const templatePath = 'mod_forum/local/grades/local/grader';
|
||||
|
||||
@@ -80,6 +79,10 @@ class UserPicker {
|
||||
// Call the showUser function to show the first user immediately.
|
||||
await this.showUser(this.currentUser);
|
||||
|
||||
// Show a list of users under the user search box.
|
||||
await this.renderSearch(this.userList);
|
||||
|
||||
this.searchResultListener();
|
||||
// Ensure that the event listeners are all bound.
|
||||
this.registerEventListeners();
|
||||
}
|
||||
@@ -120,28 +123,97 @@ class UserPicker {
|
||||
registerEventListeners() {
|
||||
this.root.addEventListener('click', async(e) => {
|
||||
const button = e.target.closest(Selectors.actions.changeUser);
|
||||
const input = e.target.closest(Selectors.actions.searchUserInput);
|
||||
|
||||
if (button) {
|
||||
const result = await this.preChangeUserCallback(this.currentUser);
|
||||
const spinner = addIconToContainerWithPromise(document.querySelector('[data-region="unified-grader"]'));
|
||||
|
||||
if (!result.failed) {
|
||||
this.updateIndex(parseInt(button.dataset.direction));
|
||||
await this.showUser(this.currentUser);
|
||||
}
|
||||
}
|
||||
if (input) {
|
||||
|
||||
spinner.resolve();
|
||||
// Make the key up a seperate function.
|
||||
this.onKeyUp(input);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for keyboard entry that'll search the user list for matching users.
|
||||
*
|
||||
* @param {Text} input User entered text of the user to search for.
|
||||
*/
|
||||
onKeyUp(input) {
|
||||
// Init a timeout variable to be used below
|
||||
let timeout = null;
|
||||
// Listen for keystroke events
|
||||
input.onkeyup = () => {
|
||||
// Clear the timeout if it has already been set.
|
||||
clearTimeout(timeout);
|
||||
// Make a new timeout set to go off in 300ms
|
||||
timeout = setTimeout(async(userList) => {
|
||||
const userInput = input.value;
|
||||
const results = userList.filter((user) => {
|
||||
return user.fullname.toLowerCase().includes(userInput.toLowerCase());
|
||||
});
|
||||
await this.renderSearch(results);
|
||||
this.searchResultListener();
|
||||
}, 300, this.userList);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the click handler for the users found in the user search area.
|
||||
*/
|
||||
searchResultListener() {
|
||||
this.root.querySelector(Selectors.actions.searchUserBox).addEventListener('click', async(e) => {
|
||||
e.preventDefault();
|
||||
const user = e.target.closest(Selectors.actions.selectUser);
|
||||
if (user !== null) {
|
||||
const foundUser = this.userList.findIndex(item => parseInt(item.id) === parseInt(user.dataset.userid));
|
||||
const result = await this.preChangeUserCallback(this.currentUser);
|
||||
|
||||
if (!result.failed) {
|
||||
this.updateIndex(0, parseInt(foundUser));
|
||||
await this.showUser(this.currentUser);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the user search results.
|
||||
*
|
||||
* @param {Array} results List of users
|
||||
*/
|
||||
async renderSearch(results) {
|
||||
const trimmedUsers = results.slice(0, 10);
|
||||
const overflowUsers = results.slice(10);
|
||||
const builtResults = {
|
||||
'expandedUsers': trimmedUsers,
|
||||
'hasCollapsed': overflowUsers.length > 0,
|
||||
'collapsedUsers': overflowUsers,
|
||||
};
|
||||
const {html, js} = await Templates.renderForPromise(`${templatePath}/user_picker/user_search`, builtResults);
|
||||
const searchUserRegion = this.root.querySelector(Selectors.actions.searchUserBox);
|
||||
Templates.replaceNode(searchUserRegion, html, js);
|
||||
}
|
||||
/**
|
||||
* Update the current user index.
|
||||
*
|
||||
* @param {Number} direction
|
||||
* @param {Number} specificIndex
|
||||
* @returns {Number}}
|
||||
*/
|
||||
updateIndex(direction) {
|
||||
this.currentUserIndex += direction;
|
||||
updateIndex(direction, specificIndex = null) {
|
||||
if (specificIndex) {
|
||||
this.currentUserIndex = specificIndex;
|
||||
} else {
|
||||
this.currentUserIndex += direction;
|
||||
}
|
||||
|
||||
// Loop around the edges.
|
||||
if (this.currentUserIndex < 0) {
|
||||
|
||||
@@ -28,6 +28,9 @@ export default {
|
||||
},
|
||||
actions: {
|
||||
changeUser: '[data-action="change-user"]',
|
||||
selectUser: '[data-action="select-user"]',
|
||||
searchUserBox: '[data-action="search-user-box"]',
|
||||
searchUserInput: '[data-action="search-user-input"]',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import {addIconToContainer} from 'core/loadingicon';
|
||||
*/
|
||||
const getComposedLayout = ({
|
||||
fullscreen = true,
|
||||
showLoader = true,
|
||||
showLoader = false,
|
||||
} = {}) => {
|
||||
const container = document.createElement('div');
|
||||
document.body.append(container);
|
||||
|
||||
@@ -729,6 +729,8 @@ $string['gradeitemnameforwholeforum'] = 'Whole forum grade for {$a->name}';
|
||||
$string['gradeitemnameforrating'] = 'Rating grade for {$a->name}';
|
||||
$string['grades:gradesavedfor'] = 'Grade saved for {$a->fullname}';
|
||||
$string['grades:gradesavefailed'] = 'Unable to save grade for {$a->fullname}: {$a->error}';
|
||||
$string['showmoreusers'] = 'Show more users';
|
||||
$string['nousersmatch'] = 'No user(s) found for given criteria';
|
||||
|
||||
// Deprecated since Moodle 3.8.
|
||||
$string['cannotdeletediscussioninsinglediscussion'] = 'You cannot delete the first post in a single discussion';
|
||||
|
||||
@@ -31,7 +31,5 @@
|
||||
}
|
||||
}}
|
||||
<div class="grader-module-content col-sm-12 col-md-8 mb-3">
|
||||
<div data-region="module_content" class="grader-module-content-display col-sm-12">
|
||||
{{> core/loading }}
|
||||
</div>
|
||||
<div data-region="module_content" class="grader-module-content-display col-sm-12"></div>
|
||||
</div>
|
||||
|
||||
@@ -53,16 +53,21 @@
|
||||
<span class="sr-only">{{#str}} next {{/str}}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link disabled" href="#" aria-label="Search" data-action="search-user">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
<li>
|
||||
<button class="btn btn-icon icon-size-4 icon-no-margin colour-inherit" aria-label="{{#str}} search {{/str}}" data-target="#searchbox" aria-expanded="false" aria-controls="searchbox" data-toggle="collapse">
|
||||
<i class="icon fa fa-search fa-fw" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}} search {{/str}}</span>
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div id="searchbox" class="col-md-12 collapse">
|
||||
<form action="" class="search-form my-3 w-100">
|
||||
<input type="text" data-action="search-user-input" class="form-control input-lg" placeholder="Search user">
|
||||
</form>
|
||||
<div data-action="search-user-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_forum/local/grades/local/grader/user_picker/user_search
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* data-action="select-user"
|
||||
|
||||
|
||||
Context variables required for this template:
|
||||
* expandedUsers: Array of users to show, limited to 10
|
||||
* profileimage: Profile image for the user
|
||||
* fullname: User's full name
|
||||
* id: User's ID
|
||||
* hasCollapsed: T/F if there are more users to show
|
||||
* collapsedUsers: Array of users after index 9
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"expandedUsers": [
|
||||
{
|
||||
"id": 4,
|
||||
"fullname": "Phillip J. Fry",
|
||||
"profileimage": "/pluginfile.php/4/user/icon/boost/f1?rev=58"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"fullname": "Turanga Leela",
|
||||
"profileimage": "/pluginfile.php/5/user/icon/boost/f1?rev=58"
|
||||
}
|
||||
],
|
||||
"hasCollapsed": true,
|
||||
"collapsedUsers": [
|
||||
{
|
||||
"id": 14,
|
||||
"fullname": "Bender B. Rodriguez",
|
||||
"profileimage": "/pluginfile.php/14/user/icon/boost/f1?rev=58"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<div data-action="search-user-box">
|
||||
{{#expandedUsers}}
|
||||
<div class="mt-2">
|
||||
<img class="rounded-circle userpicture" src="{{profileimage}}"
|
||||
alt="{{#str}}pictureof, moodle, {{fullname}}{{/str}}"
|
||||
title="{{#str}}pictureof, moodle, {{fullname}}{{/str}}" >
|
||||
<a href="#" class="font-weight-bold" data-action="select-user" data-userid="{{id}}">{{fullname}}</a>
|
||||
</div>
|
||||
{{/expandedUsers}}
|
||||
{{^expandedUsers}}
|
||||
<h5>{{#str}}nousersmatch, mod_forum{{/str}}</h5>
|
||||
{{/expandedUsers}}
|
||||
{{#hasCollapsed}}
|
||||
<button
|
||||
class="btn btn-icon mt-2 text-muted icon-no-margin icon-size-3"
|
||||
type="button"
|
||||
id="grader-users-menu-{{uniqid}}"
|
||||
aria-label="{{#str}} showmoreusers, mod_forum {{/str}}"
|
||||
data-toggle="collapse"
|
||||
data-target="#collapsed-users-{{uniqid}}"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapsed-users-{{uniqid}}"
|
||||
>
|
||||
{{#pix}} i/moremenu {{/pix}}
|
||||
</button>
|
||||
<div id="collapsed-users-{{uniqid}}" class="collapse" aria-labelledby="grader-users-menu-{{uniqid}}">
|
||||
{{#collapsedUsers}}
|
||||
<div class="mt-2">
|
||||
<img class="rounded-circle userpicture" src="{{profileimage}}"
|
||||
alt="{{#str}}pictureof, moodle, {{fullname}}{{/str}}"
|
||||
title="{{#str}}pictureof, moodle, {{fullname}}{{/str}}" >
|
||||
<a href="#" class="font-weight-bold" data-action="select-user" data-userid="{{id}}">{{fullname}}</a>
|
||||
</div>
|
||||
{{/collapsedUsers}}
|
||||
</div>
|
||||
{{/hasCollapsed}}
|
||||
</div>
|
||||
Reference in New Issue
Block a user