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: {
*/
const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {
return async(user) => {
+ const spinner = addIconToContainerWithPromise(root);
const [
[html, js],
userGrade,
gradingPanelJS
] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);
Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanel), gradingPanelHtml, gradingPanelJS);
+ spinner.resolve();
};
};
import Templates from 'core/templates';
import Selectors from './user_picker/selectors';
-import {addIconToContainerWithPromise} from 'core/loadingicon';
const templatePath = 'mod_forum/local/grades/local/grader';
// 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();
}
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) {
+
+ // 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);
+ };
+ }
- spinner.resolve();
+ /**
+ * 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) {
},
actions: {
changeUser: '[data-action="change-user"]',
+ selectUser: '[data-action="select-user"]',
+ searchUserBox: '[data-action="search-user-box"]',
+ searchUserInput: '[data-action="search-user-input"]',
}
};
*/
const getComposedLayout = ({
fullscreen = true,
- showLoader = true,
+ showLoader = false,
} = {}) => {
const container = document.createElement('div');
document.body.append(container);
$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';
}
}}
<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>
<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>
-
--- /dev/null
+{{!
+ 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>