mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 00:42:54 +02:00
user selection: MDL-16996 Improve the user selector used on the assign roles and group memebers pages - Write the JavaScript to do the Ajax requests and update the list of users.
This commit is contained in:
parent
b61d8ee50d
commit
d56f9e659d
@ -316,6 +316,7 @@ $string['modulemissingcode'] = 'Module $a is missing the code needed to perform
|
||||
$string['modulerequirementsnotmet'] = 'Module \"$a->modulename\" ($a->moduleversion) could not be installed. It requires a newer version of Moodle (currently you are using $a->currentmoodle, you need $a->requiremoodle).';
|
||||
$string['mustbeteacher'] = 'You must be a teacher to look at this page';
|
||||
$string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!';
|
||||
$string['mustbeloggedin'] = 'You must be logged in to do this';
|
||||
$string['needphpext'] = 'You need to add $a support to your PHP installation';
|
||||
$string['needcopy'] = 'You need to copy something first!';
|
||||
$string['needcoursecategroyid'] = 'Either course id or category must be specified';
|
||||
@ -410,6 +411,7 @@ $string['unknownhelp'] = 'Unknown help topic $a';
|
||||
$string['unknowngroup'] = 'Unknown group \"$a\"';
|
||||
$string['unknownrole'] = 'Unknown role \"$a\"';
|
||||
$string['unknownuseraction'] = 'Sorry, I do not understand this user action';
|
||||
$string['unknownuserselector'] = 'Unknown user selector';
|
||||
$string['unknownmodulename'] = 'Unknown module name for form';
|
||||
$string['unknoworder'] = 'Unknown ordering';
|
||||
$string['unknowparamtype'] = 'Unknown parameter type: $a';
|
||||
|
@ -123,9 +123,6 @@ abstract class user_selector_base {
|
||||
public function display($return = false) {
|
||||
global $USER, $CFG;
|
||||
|
||||
// Ensure that the list of previously selected users is up to date.
|
||||
$this->get_selected_users();
|
||||
|
||||
// Get the list of requested users, and if there is only one, set a flag to autoselect it.
|
||||
$search = optional_param($this->name . '_searchtext', '', PARAM_RAW);
|
||||
$groupedusers = $this->find_users($search);
|
||||
@ -146,12 +143,9 @@ abstract class user_selector_base {
|
||||
$output = '<div class="userselector" id="' . $this->name . '_wrapper">' . "\n" .
|
||||
'<select name="' . $name . '" id="' . $this->name . '" ' .
|
||||
$multiselect . 'size="' . $this->rows . '">' . "\n";
|
||||
foreach ($groupedusers as $groupname => $users) {
|
||||
$output .= $this->output_optgroup($groupname, $users, $select);
|
||||
}
|
||||
if (!empty($this->selected)) {
|
||||
$output .= $this->output_optgroup(get_string('previouslyselectedusers'), $this->selected, true);
|
||||
}
|
||||
|
||||
// Populate the select.
|
||||
$output .= $this->output_options($groupedusers, $select);
|
||||
|
||||
// Output the search controls.
|
||||
$output .= "</select>\n<div>\n";
|
||||
@ -161,16 +155,8 @@ abstract class user_selector_base {
|
||||
$this->name . '_searchbutton" value="' . $this->search_button_caption() . '" />';
|
||||
$output .= "</div>\n</div>\n\n";
|
||||
|
||||
// This method trashes $this->selected, so reset it so if someone tries to
|
||||
// Use it again, it is rebuilt.
|
||||
$this->selected = null;
|
||||
|
||||
// Put the options into the session for the benefit of the ajax code.
|
||||
$options = $this->get_options();
|
||||
$hash = md5(serialize($options));
|
||||
$USER->userselectors[$hash] = $options;
|
||||
$output .= '<p><a href="' . $CFG->wwwroot . '/user/selector/search.php?selectorid=' .
|
||||
$hash . '&' . 'sesskey=' . sesskey() . '&search=">Ajax search script</a></p>'; // DONOTCOMMIT
|
||||
// Initialise the ajax functionality.
|
||||
$output .= $this->initialise_javascript();
|
||||
|
||||
// Return or output it.
|
||||
if ($return) {
|
||||
@ -323,6 +309,38 @@ abstract class user_selector_base {
|
||||
return array(implode(' AND ', $tests), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the list of <optgroup>s and <options>s that go inside the select.
|
||||
* This method should do the same as the JavaScript method
|
||||
* user_selector.prototype.handle_response.
|
||||
*
|
||||
* @param unknown_type $groupedusers
|
||||
* @param unknown_type $select
|
||||
* @return unknown
|
||||
*/
|
||||
protected function output_options($groupedusers, $select) {
|
||||
$output = '';
|
||||
|
||||
// Ensure that the list of previously selected users is up to date.
|
||||
$this->get_selected_users();
|
||||
|
||||
// Output each optgroup.
|
||||
foreach ($groupedusers as $groupname => $users) {
|
||||
$output .= $this->output_optgroup($groupname, $users, $select);
|
||||
}
|
||||
|
||||
// If there were previously selected users who do not match the search, show them too.
|
||||
if (!empty($this->selected)) {
|
||||
$output .= $this->output_optgroup(get_string('previouslyselectedusers'), $this->selected, true);
|
||||
}
|
||||
|
||||
// This method trashes $this->selected, so clear the cache so it is
|
||||
// rebuilt before anyone tried to use it again.
|
||||
$this->selected = null;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
protected function output_optgroup($groupname, $users, $select) {
|
||||
$output = '<optgroup label="' . s($groupname) . ' (' . count($users) . ')">' . "\n";
|
||||
if (!empty($users)) {
|
||||
@ -344,10 +362,10 @@ abstract class user_selector_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a user object to a string suitable for displaying as an option in the dropdown.
|
||||
* Convert a user object to a string suitable for displaying as an option in the list box.
|
||||
*
|
||||
* @param object $user the user to display.
|
||||
* @return string a string representation to display.
|
||||
* @return string a string representation of the user.
|
||||
*/
|
||||
protected function output_user($user) {
|
||||
$bits = array(
|
||||
@ -365,9 +383,38 @@ abstract class user_selector_base {
|
||||
protected function search_button_caption() {
|
||||
return get_string('search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
*/
|
||||
protected function initialise_javascript() {
|
||||
global $USER;
|
||||
$output = '';
|
||||
|
||||
// Required JavaScript code.
|
||||
require_js(array('yui_yahoo', 'yui_event', 'yui_json', 'yui_connection', 'yui_datasource'));
|
||||
require_js('user/selector/script.js');
|
||||
|
||||
// Put the options into the session, to allow search.php to respond to the ajax requests.
|
||||
$options = $this->get_options();
|
||||
$hash = md5(serialize($options));
|
||||
$USER->userselectors[$hash] = $options;
|
||||
|
||||
// Initialise the selector.
|
||||
$output .= print_js_call('new user_selector', array($this->name, $hash,
|
||||
sesskey(), $this->extrafields, get_string('previouslyselectedusers')), true);
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
class role_assign_user_selector extends user_selector_base {
|
||||
class role_assign_potential_user_selector extends user_selector_base {
|
||||
public function find_users($search) {
|
||||
return array(); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
class role_assign_current_user_selector extends user_selector_base {
|
||||
public function find_users($search) {
|
||||
return array(); // TODO
|
||||
}
|
||||
|
270
user/selector/script.js
Normal file
270
user/selector/script.js
Normal file
@ -0,0 +1,270 @@
|
||||
// JavaScript for the user selectors.
|
||||
// This is somewhat inspired by the autocomplete component in YUI.
|
||||
// license: http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
// package: userselector
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function user_selector(name, hash, sesskey, extrafields, strprevselected) {
|
||||
this.name = name;
|
||||
this.extrafields = extrafields;
|
||||
this.strprevselected = strprevselected;
|
||||
|
||||
// Set up the data source.
|
||||
this.datasource = new YAHOO.util.XHRDataSource(moodle_cfg.wwwroot +
|
||||
'/user/selector/search.php?selectorid=' + hash + '&sesskey=' + sesskey + '&search=');
|
||||
this.datasource.connXhrMode = 'cancelStaleRequests';
|
||||
this.datasource.responseType = YAHOO.util.XHRDataSource.TYPE_JSON;
|
||||
this.datasource.responseSchema = {resultsList: 'results'};
|
||||
|
||||
// Find some key HTML elements.
|
||||
this.searchfield = document.getElementById(this.name + '_searchtext');
|
||||
this.listbox = document.getElementById(this.name);
|
||||
|
||||
// Hide the search button and replace it with a label.
|
||||
var searchbutton = document.getElementById(this.name + '_searchbutton');
|
||||
var label = document.createElement('label');
|
||||
label.for = this.name + '_searchtext';
|
||||
label.appendChild(document.createTextNode(searchbutton.value));
|
||||
this.searchfield.parentNode.insertBefore(label, this.searchfield);
|
||||
searchbutton.parentNode.removeChild(searchbutton);
|
||||
|
||||
// Hook up the event handler.
|
||||
var oself = this;
|
||||
YAHOO.util.Event.addListener(this.searchfield, "keyup", function(e) { oself.handle_keyup() });
|
||||
this.lastsearch = this.get_search_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* This id/name used for this control in the HTML.
|
||||
* @property name
|
||||
* @type String
|
||||
*/
|
||||
user_selector.prototype.name = null;
|
||||
|
||||
/**
|
||||
* Array of fields to display for each user, in addition to fullname.
|
||||
* @property extrafields
|
||||
* @type Array
|
||||
*/
|
||||
user_selector.prototype.extrafields = [];
|
||||
|
||||
/**
|
||||
* The datasource used to fetch lists of users from Moodle.
|
||||
* @property datasource
|
||||
* @type YAHOO.widget.DataSource
|
||||
*/
|
||||
user_selector.prototype.datasource = null;
|
||||
|
||||
/**
|
||||
* Number of seconds to delay before submitting a query request. If a query
|
||||
* request is received before a previous one has completed its delay, the
|
||||
* previous request is cancelled and the new request is set to the delay.
|
||||
*
|
||||
* @property querydelay
|
||||
* @type Number
|
||||
* @default 0.2
|
||||
*/
|
||||
user_selector.prototype.querydelay = 0.2;
|
||||
|
||||
/**
|
||||
* The input element that contains the search term.
|
||||
*
|
||||
* @property searchfield
|
||||
* @type HTMLInputElement
|
||||
*/
|
||||
user_selector.prototype.searchfield = 0.2;
|
||||
|
||||
/**
|
||||
* The select element that contains the list of users.
|
||||
*
|
||||
* @property listbox
|
||||
* @type HTMLSelectElement
|
||||
*/
|
||||
user_selector.prototype.listbox = null;
|
||||
|
||||
/**
|
||||
* Used to hold the timeout id of the timeout that waits before doing a search.
|
||||
*
|
||||
* @property timeoutid
|
||||
* @type Number
|
||||
*/
|
||||
user_selector.prototype.timeoutid = null;
|
||||
|
||||
/**
|
||||
* The last string that we searched for.
|
||||
*
|
||||
* @property lastsearch
|
||||
* @type String
|
||||
*/
|
||||
user_selector.prototype.lastsearch = null;
|
||||
|
||||
/**
|
||||
* Name of the previously selected users group.
|
||||
*
|
||||
* @property strprevselected
|
||||
* @type String
|
||||
*/
|
||||
user_selector.prototype.strprevselected = '';
|
||||
|
||||
/**
|
||||
* Used to track whether there is only one optoin matchin the search results, if
|
||||
* so, it is automatically selected.
|
||||
*
|
||||
* @property strprevselected
|
||||
* @type Object
|
||||
**/
|
||||
user_selector.prototype.onlyoption = null;
|
||||
|
||||
/**
|
||||
* Key up hander for the search text box. Trigger an ajax search after a delay.
|
||||
*/
|
||||
user_selector.prototype.handle_keyup = function() {
|
||||
if (this.timeoutid) {
|
||||
clearTimeout(this.timeoutid);
|
||||
this.timeoutid = null;
|
||||
}
|
||||
var oself = this;
|
||||
this.timeoutid = setTimeout(function() { oself.send_query() }, this.querydelay * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String the value to search for, with leading and trailing whitespace trimmed.
|
||||
*/
|
||||
user_selector.prototype.get_search_text = function() {
|
||||
return this.searchfield.value.replace(/^ +| +$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires off the ajax search request.
|
||||
*/
|
||||
user_selector.prototype.send_query = function() {
|
||||
var value = this.get_search_text();
|
||||
if (this.lastsearch == value) {
|
||||
return;
|
||||
}
|
||||
this.datasource.sendRequest(this.searchfield.value, {
|
||||
success: this.handle_response,
|
||||
failure: this.handle_failure,
|
||||
scope: this
|
||||
});
|
||||
this.lastsearch = value;
|
||||
this.listbox.style.background = 'url(' + moodle_cfg.pixpath + '/i/loading.gif) no-repeat center center';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when we get some data back from the search.
|
||||
* @param Object request not used.
|
||||
* @param Object data the list of users that was returned.
|
||||
*/
|
||||
user_selector.prototype.handle_response = function(request, data) {
|
||||
this.listbox.style.background = '';
|
||||
this.output_options(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles what happens when the ajax request fails.
|
||||
*/
|
||||
user_selector.prototype.handle_failure = function() {
|
||||
this.listbox.style.background = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should do the same sort of thing as the PHP method
|
||||
* user_selector_base::output_options.
|
||||
* @param object data the list of users to populate the list box with.
|
||||
*/
|
||||
user_selector.prototype.output_options = function(data) {
|
||||
// Clear out the existing options, keeping any ones that are already selected.
|
||||
this.selected = {};
|
||||
var groups = this.listbox.getElementsByTagName('optgroup');
|
||||
while (groups.length > 0) {
|
||||
var optgroup = groups[0]; // Remeber that groups is a live array as we remove optgroups from the select, it updates.
|
||||
var options = optgroup.getElementsByTagName('option');
|
||||
while (options.length > 0) {
|
||||
var option = options[0];
|
||||
if (option.selected) {
|
||||
var optiontext = option.innerText || option.textContent
|
||||
this.selected[option.value] = { id: option.value, formatted: optiontext };
|
||||
}
|
||||
optgroup.removeChild(option);
|
||||
}
|
||||
this.listbox.removeChild(optgroup);
|
||||
}
|
||||
|
||||
var results = data.results[0];
|
||||
|
||||
// Output each optgroup.
|
||||
this.onlyoption = null;
|
||||
for (groupname in results) {
|
||||
this.output_group(groupname, results[groupname], false);
|
||||
}
|
||||
|
||||
// If there was only one option matching the search results, select it.
|
||||
if (this.onlyoption) {
|
||||
this.onlyoption.selected = true;
|
||||
}
|
||||
this.onlyoption = null;
|
||||
|
||||
// If there were previously selected users who do not match the search, show them too.
|
||||
var areprevselected = false;
|
||||
for (user in this.selected) {
|
||||
areprevselected = true;
|
||||
break;
|
||||
}
|
||||
if (areprevselected) {
|
||||
this.output_group(this.strprevselected, this.selected, true);
|
||||
}
|
||||
this.selected = null;
|
||||
|
||||
}
|
||||
|
||||
user_selector.prototype.output_group = function(groupname, users, select) {
|
||||
var optgroup = document.createElement('optgroup');
|
||||
optgroup.label = groupname;
|
||||
var count = 0;
|
||||
for (var userid in users) {
|
||||
var user = users[userid];
|
||||
var option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.appendChild(document.createTextNode(this.output_user(user)));
|
||||
if (select || this.selected[user.id]) {
|
||||
option.selected = 'selected';
|
||||
}
|
||||
delete this.selected[user.id];
|
||||
optgroup.appendChild(option);
|
||||
if (this.onlyoption === null) {
|
||||
this.onlyoption = option;
|
||||
} else {
|
||||
this.onlyoption = false;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
if (count == 0) {
|
||||
var option = document.createElement('option');
|
||||
option.disabled = 'disabled';
|
||||
option.appendChild(document.createTextNode('\u00A0'));
|
||||
optgroup.appendchild(option);
|
||||
}
|
||||
optgroup.label += ' (' + count + ')';
|
||||
this.listbox.appendChild(optgroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a user object to a string suitable for displaying as an option in the list box.
|
||||
*
|
||||
* @param Object user the user to display.
|
||||
* @return string a string representation of the user.
|
||||
*/
|
||||
user_selector.prototype.output_user = function(user) {
|
||||
if (user.formatted) {
|
||||
return user.formatted;
|
||||
}
|
||||
var output = user.fullname;
|
||||
for (var i = 0; i < this.extrafields.length; i++) {
|
||||
output += ', ' + user[this.extrafields[i]];
|
||||
}
|
||||
return output;
|
||||
}
|
@ -34,7 +34,9 @@ require_once(dirname(__FILE__) . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/user/selector/lib.php');
|
||||
|
||||
// Check access.
|
||||
require_login();
|
||||
if (!isloggedin()) {;
|
||||
print_error('mustbeloggedin');
|
||||
}
|
||||
if (!confirm_sesskey()) {
|
||||
print_error('invalidsesskey');
|
||||
}
|
||||
@ -62,5 +64,13 @@ $userselector = new $classname($name, $options);
|
||||
|
||||
// Do the search and output the results.
|
||||
$users = $userselector->find_users($search);
|
||||
foreach ($users as &$group) {
|
||||
foreach ($group as &$user) {
|
||||
$user->fullname = fullname($user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
header('Content-type: application/json');
|
||||
echo json_encode(array('results' => $users));
|
||||
?>
|
@ -14,7 +14,8 @@ class test_user_selector extends user_selector_base {
|
||||
list($wherecondition, $params) = $this->search_sql($search, 'u');
|
||||
$sql = 'SELECT ' . $this->required_fields_sql('u') .
|
||||
' FROM {user} u' .
|
||||
' WHERE ' . $wherecondition;
|
||||
' WHERE ' . $wherecondition .
|
||||
' ORDER BY u.lastname, u.firstname';
|
||||
$users = $DB->get_recordset_sql($sql, $params);
|
||||
$groupedusers = array();
|
||||
if ($search) {
|
||||
@ -40,6 +41,8 @@ if ($justdefineclass) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_login();
|
||||
require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
|
||||
print_header();
|
||||
|
||||
$userselector = new test_user_selector('myuserselector');
|
||||
|
Loading…
x
Reference in New Issue
Block a user