mirror of
https://github.com/moodle/moodle.git
synced 2025-06-02 14:15:11 +02:00
user selection: MDL-17073 add options to control the search in a collapsible region.
This commit is contained in:
parent
67c8a3e870
commit
904998d8f9
@ -1325,6 +1325,7 @@ $string['search'] = 'Search';
|
||||
$string['searchagain'] = 'Search again';
|
||||
$string['searchcourses'] = 'Search courses';
|
||||
$string['searchhelp'] = 'You can search for multiple words at once.<br /><br />word : find any match of this word within the text.<br />+word : only exact matching words will be found.<br />-word : don\'t include results containing this word.';
|
||||
$string['searchoptions'] = 'Search options';
|
||||
$string['searchresults'] = 'Search results';
|
||||
$string['sec'] = 'sec';
|
||||
$string['secondstotime172800'] = '2 days';
|
||||
@ -1593,6 +1594,9 @@ $string['usernotconfirmed'] = 'Could not confirm $a';
|
||||
$string['userpic'] = 'User picture';
|
||||
$string['userprofilefor'] = 'User profile for $a';
|
||||
$string['users'] = 'Users';
|
||||
$string['userselectorpreserveselected'] = 'Keep selected users, even if they no longer match the search';
|
||||
$string['userselectorautoselectunique'] = 'If only one user matches the search, select them automatically';
|
||||
$string['userselectorsearchanywhere'] = 'Match the search text anywhere in the user\'s name';
|
||||
$string['usersnew'] = 'New users';
|
||||
$string['usersnoaccesssince'] = 'Inactive for more than';
|
||||
$string['userzones'] = 'User zones';
|
||||
|
@ -607,7 +607,7 @@ function collapsible_region(id, userpref, strtooltip) {
|
||||
|
||||
// Find the divs in the document.
|
||||
this.div = document.getElementById(id);
|
||||
this.innerdiv = document.getElementById(id + '_inner');
|
||||
this.innerdiv = document.getElementById(id + '_sizer');
|
||||
this.caption = document.getElementById(id + '_caption');
|
||||
this.caption.title = strtooltip;
|
||||
|
||||
@ -641,8 +641,11 @@ function collapsible_region(id, userpref, strtooltip) {
|
||||
a.appendChild(this.icon);
|
||||
|
||||
// Hook up the event handler.
|
||||
self = this;
|
||||
var self = this;
|
||||
YAHOO.util.Event.addListener(a, 'click', function(e) {self.handle_click(e);});
|
||||
|
||||
// Handler for the animation finishing.
|
||||
this.animation.onComplete.subscribe(function() {self.handle_animation_complete();});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -681,6 +684,7 @@ collapsible_region.prototype.collapsed = false;
|
||||
*/
|
||||
collapsible_region.prototype.animation = null;
|
||||
|
||||
/** When clicked, toggle the collapsed state, and trigger the animation. */
|
||||
collapsible_region.prototype.handle_click = function(e) {
|
||||
// Toggle the state.
|
||||
this.collapsed = !this.collapsed;
|
||||
@ -694,7 +698,6 @@ collapsible_region.prototype.handle_click = function(e) {
|
||||
}
|
||||
if (this.collapsed) {
|
||||
var targetel = this.caption;
|
||||
this.div.className += ' collapsed';
|
||||
} else {
|
||||
var targetel = this.innerdiv;
|
||||
this.div.className = this.div.className.replace(/\s*\bcollapsed\b\s*/, ' ');
|
||||
@ -715,3 +718,10 @@ collapsible_region.prototype.handle_click = function(e) {
|
||||
set_user_preference(this.userpref, this.collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/** When when the animation is finished, add the collapsed class name in relevant. */
|
||||
collapsible_region.prototype.handle_animation_complete = function() {
|
||||
if (this.collapsed) {
|
||||
this.div.className += ' collapsed';
|
||||
}
|
||||
}
|
@ -4166,7 +4166,7 @@ function print_collapsible_region_start($classes, $id, $caption, $userpref = fal
|
||||
* @return mixed if $return is false, returns nothing, otherwise returns a string of HTML.
|
||||
*/
|
||||
function print_collapsible_region_end($return = false) {
|
||||
$output = '</div></div>';
|
||||
$output = '</div></div></div>';
|
||||
|
||||
if ($return) {
|
||||
return $output;
|
||||
|
@ -226,6 +226,9 @@ div.collapsibleregion div.collapsibleregioncaption a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.jsenabled .collapsed .collapsibleregioninner {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.noticebox {
|
||||
border-width:1px;
|
||||
@ -570,7 +573,12 @@ div.hide {
|
||||
.userselector div label {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
#userselector_options {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
#userselector_options .collapsibleregioncaption {
|
||||
font-weight: bold;
|
||||
}
|
||||
/***
|
||||
*** Forms
|
||||
***/
|
||||
@ -1049,7 +1057,7 @@ body#admin-modules table.generaltable td.c0
|
||||
width: 100%;
|
||||
}
|
||||
.roleassigntable td {
|
||||
vertical-align: middle;
|
||||
vertical-align: top;
|
||||
padding: 0.2em 0.3em;
|
||||
}
|
||||
.roleassigntable p {
|
||||
@ -1083,6 +1091,7 @@ body#admin-modules table.generaltable td.c0
|
||||
font-weight: bold;
|
||||
}
|
||||
.roleassigntable #buttonscell #addcontrols {
|
||||
margin-top: 3em;
|
||||
height: 13em;
|
||||
}
|
||||
.roleassigntable #removeselect_wrapper,
|
||||
|
@ -55,10 +55,22 @@ abstract class user_selector_base {
|
||||
protected $exclude = array();
|
||||
/** A list of the users who are selected. */
|
||||
protected $selected = null;
|
||||
/** When the search changes, do we keep previously selected options that do
|
||||
* not match the new search term? */
|
||||
protected $preserveselected = false;
|
||||
/** If only one user matches the search, should we select them automatically. */
|
||||
protected $autoselectunique = false;
|
||||
/** When searching, do we only match the starts of fields (better performace)
|
||||
* or do we match occurrences anywhere? */
|
||||
protected $searchanywhere = false;
|
||||
|
||||
// This is used by get selected users,
|
||||
private $validatinguserids = null;
|
||||
// Used to ensure we only output the search options for one user selector on
|
||||
// each page.
|
||||
private static $searchoptionsoutput = false;
|
||||
|
||||
|
||||
// Public API ==============================================================
|
||||
|
||||
/**
|
||||
@ -81,6 +93,9 @@ abstract class user_selector_base {
|
||||
if (isset($options['exclude']) && is_array($options['exclude'])) {
|
||||
$this->exclude = $options['exclude'];
|
||||
}
|
||||
$this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected);
|
||||
$this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique);
|
||||
$this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,10 +181,23 @@ abstract class user_selector_base {
|
||||
$this->name . '_searchbutton" value="' . $this->search_button_caption() . '" />';
|
||||
$output .= '<input type="submit" name="' . $this->name . '_clearbutton" id="' .
|
||||
$this->name . '_clearbutton" value="' . get_string('clear') . '" />';
|
||||
|
||||
// And the search options.
|
||||
$optionsoutput = false;
|
||||
if (!user_selector_base::$searchoptionsoutput) {
|
||||
$output .= print_collapsible_region_start('', 'userselector_options',
|
||||
get_string('searchoptions'), 'userselector_optionscollapsed', true, true);
|
||||
$output .= $this->option_checkbox('preserveselected', $this->preserveselected, get_string('userselectorpreserveselected'));
|
||||
$output .= $this->option_checkbox('autoselectunique', $this->autoselectunique, get_string('userselectorautoselectunique'));
|
||||
$output .= $this->option_checkbox('searchanywhere', $this->searchanywhere, get_string('userselectorsearchanywhere'));
|
||||
$output .= print_collapsible_region_end(true);
|
||||
user_selector_base::$searchoptionsoutput = true;
|
||||
$optionsoutput = true;
|
||||
}
|
||||
$output .= "</div>\n</div>\n\n";
|
||||
|
||||
// Initialise the ajax functionality.
|
||||
$output .= $this->initialise_javascript();
|
||||
$output .= $this->initialise_javascript($optionsoutput);
|
||||
|
||||
// Return or output it.
|
||||
if ($return) {
|
||||
@ -345,9 +373,14 @@ abstract class user_selector_base {
|
||||
$conditions[] = $u . $field;
|
||||
}
|
||||
$ilike = ' ' . $DB->sql_ilike() . ' ?';
|
||||
if ($this->searchanywhere) {
|
||||
$searchparam = '%' . $search . '%';
|
||||
} else {
|
||||
$searchparam = $search . '%';
|
||||
}
|
||||
foreach ($conditions as &$condition) {
|
||||
$condition .= $ilike;
|
||||
$params[] = $search . '%';
|
||||
$params[] = $searchparam;
|
||||
}
|
||||
$tests[] = '(' . implode(' OR ', $conditions) . ')';
|
||||
}
|
||||
@ -393,12 +426,13 @@ abstract class user_selector_base {
|
||||
// Ensure that the list of previously selected users is up to date.
|
||||
$this->get_selected_users();
|
||||
|
||||
// If $groupedusers is empty, make a 'no matching users' group. If there
|
||||
// is only one selected user, set a flag to select them.
|
||||
// If $groupedusers is empty, make a 'no matching users' group. If there is
|
||||
// only one selected user, set a flag to select them if that option is turned on.
|
||||
$select = false;
|
||||
if (empty($groupedusers)) {
|
||||
$groupedusers = array(get_string('nomatchingusers', '', $search) => array());
|
||||
} else if (count($groupedusers) == 1 && count(reset($groupedusers)) == 1) {
|
||||
} else if ($this->autoselectunique && count($groupedusers) == 1 &&
|
||||
count(reset($groupedusers)) == 1) {
|
||||
$select = true;
|
||||
if (!$this->multiselect) {
|
||||
$this->selected = array();
|
||||
@ -411,7 +445,7 @@ abstract class user_selector_base {
|
||||
}
|
||||
|
||||
// If there were previously selected users who do not match the search, show them too.
|
||||
if (!empty($this->selected)) {
|
||||
if ($this->preserveselected && !empty($this->selected)) {
|
||||
$output .= $this->output_optgroup(get_string('previouslyselectedusers', '', $search), $this->selected, true);
|
||||
}
|
||||
|
||||
@ -475,12 +509,39 @@ abstract class user_selector_base {
|
||||
return get_string('search');
|
||||
}
|
||||
|
||||
// Initialise one of the option checkboxes, either from
|
||||
// the request, or failing that from the user_preferences table, or
|
||||
// finally from the given default.
|
||||
private function initialise_option($name, $default) {
|
||||
$param = optional_param($name, null, PARAM_BOOL);
|
||||
if (is_null($param)) {
|
||||
return get_user_preferences($name, $default);
|
||||
} else {
|
||||
set_user_preference($name, $param);
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
|
||||
// Output one of the options checkboxes.
|
||||
private function option_checkbox($name, $on, $label) {
|
||||
if ($on) {
|
||||
$checked = ' checked="checked"';
|
||||
} else {
|
||||
$checked = '';
|
||||
}
|
||||
$name = 'userselector_' . $name;
|
||||
$output = '<p><input type="hidden" name="' . $name . '" value="0" />' .
|
||||
'<input type="checkbox" id="' . $name . '" name="' . $name . '" value="1"' . $checked . ' /> ' .
|
||||
'<label for="' . $name . '">' . $label . "</label></p>\n";
|
||||
user_preference_allow_ajax_update($name, PARAM_BOOL);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param boolean $optiontracker if true, initialise JavaScript for updating the user prefs.
|
||||
* @return any HTML needed here.
|
||||
*/
|
||||
protected function initialise_javascript() {
|
||||
protected function initialise_javascript($optiontracker) {
|
||||
global $USER;
|
||||
$output = '';
|
||||
|
||||
@ -495,8 +556,14 @@ abstract class user_selector_base {
|
||||
|
||||
// Initialise the selector.
|
||||
$output .= print_js_call('new user_selector', array($this->name, $hash,
|
||||
sesskey(), $this->extrafields, get_string('previouslyselectedusers', '', '%%SEARCHTERM%%'),
|
||||
$this->extrafields, get_string('previouslyselectedusers', '', '%%SEARCHTERM%%'),
|
||||
get_string('nomatchingusers', '', '%%SEARCHTERM%%')), true);
|
||||
|
||||
// Initialise the options tracker, if they are our responsibility.
|
||||
if ($optiontracker) {
|
||||
$output .= print_js_call('new user_selector_options_tracker', array(), true);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,16 @@
|
||||
* @constructor
|
||||
* @param String name the control name/id.
|
||||
* @param String hash the hash that identifies this selector in the user's session.
|
||||
* @param String sesskey the user's sesskey.
|
||||
* @param Array extrafields extra fields we are displaying for each user in addition to fullname.
|
||||
* @param String label used for the optgroup of users who are selected but who do not match the current search.
|
||||
*/
|
||||
function user_selector(name, hash, sesskey, extrafields, strprevselected, strnomatchingusers) {
|
||||
function user_selector(name, hash, extrafields, strprevselected, strnomatchingusers) {
|
||||
this.name = name;
|
||||
this.extrafields = extrafields;
|
||||
this.strprevselected = strprevselected;
|
||||
this.strnomatchingusers = strnomatchingusers;
|
||||
this.searchurl = moodle_cfg.wwwroot + '/user/selector/search.php?selectorid=' +
|
||||
hash + '&sesskey=' + sesskey + '&search='
|
||||
hash + '&sesskey=' + moodle_cfg.sesskey + '&search='
|
||||
|
||||
// Set up the data source.
|
||||
this.datasource = new YAHOO.util.XHRDataSource(this.searchurl);
|
||||
@ -53,6 +52,9 @@ function user_selector(name, hash, sesskey, extrafields, strprevselected, strnom
|
||||
YAHOO.util.Event.addListener(this.listbox, "click", function(e) { oself.handle_selection_change() });
|
||||
YAHOO.util.Event.addListener(this.listbox, "change", function(e) { oself.handle_selection_change() });
|
||||
|
||||
// And when the search any substring preference changes. Do an immediate research.
|
||||
YAHOO.util.Event.addListener('userselector_searchanywhere', "click", function(e) { oself.handle_searchanywhere_click() });
|
||||
|
||||
// Replace the Clear submit button with a clone that is not a submit button.
|
||||
var oldclearbutton = document.getElementById(this.name + '_clearbutton');
|
||||
this.clearbutton = document.createElement('input');
|
||||
@ -129,7 +131,7 @@ user_selector.prototype.strnomatchingusers = '';
|
||||
* @type Number
|
||||
* @default 0.2
|
||||
*/
|
||||
user_selector.prototype.querydelay = 0.2;
|
||||
user_selector.prototype.querydelay = 0.5;
|
||||
|
||||
// Internal fields =============================================================
|
||||
|
||||
@ -224,7 +226,7 @@ user_selector.prototype.handle_keyup = function(e) {
|
||||
// Trigger an ajax search after a delay.
|
||||
this.cancel_timeout();
|
||||
var oself = this;
|
||||
this.timeoutid = setTimeout(function() { oself.send_query() }, this.querydelay * 1000);
|
||||
this.timeoutid = setTimeout(function() { oself.send_query(false) }, this.querydelay * 1000);
|
||||
|
||||
// Enable or diable the clear button.
|
||||
this.clearbutton.disabled = this.get_search_text() == '';
|
||||
@ -247,12 +249,21 @@ user_selector.prototype.cancel_timeout = function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Key up hander for the search text box.
|
||||
* Click handler for the clear button..
|
||||
*/
|
||||
user_selector.prototype.handle_clear = function() {
|
||||
this.searchfield.value = '';
|
||||
this.clearbutton.disabled = true;
|
||||
this.send_query();
|
||||
this.send_query(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a re-search when the 'search any substring' option is changed.
|
||||
*/
|
||||
user_selector.prototype.handle_searchanywhere_click = function() {
|
||||
if (this.lastsearch != '' && this.get_search_text() != '') {
|
||||
this.send_query(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,19 +273,31 @@ user_selector.prototype.get_search_text = function() {
|
||||
return this.searchfield.value.replace(/^ +| +$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of one of the option checkboxes.<b>
|
||||
*/
|
||||
user_selector.prototype.get_option = function(name) {
|
||||
var checkbox = document.getElementById('userselector_' + name);
|
||||
if (checkbox) {
|
||||
return checkbox.checked;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires off the ajax search request.
|
||||
*/
|
||||
user_selector.prototype.send_query = function() {
|
||||
user_selector.prototype.send_query = function(forceresearch) {
|
||||
// Cancel any pending timeout.
|
||||
this.cancel_timeout();
|
||||
|
||||
var value = this.get_search_text();
|
||||
this.searchfield.className = '';
|
||||
if (this.lastsearch == value) {
|
||||
if (this.lastsearch == value && !forceresearch) {
|
||||
return;
|
||||
}
|
||||
this.datasource.sendRequest(this.searchfield.value, {
|
||||
this.datasource.sendRequest(value + '&userselector_searchanywhere=' + this.get_option('searchanywhere'), {
|
||||
success: this.handle_response,
|
||||
failure: this.handle_failure,
|
||||
scope: this
|
||||
@ -349,12 +372,13 @@ 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');
|
||||
var preserveselected = this.get_option('preserveselected');
|
||||
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) {
|
||||
if (preserveselected && option.selected) {
|
||||
var optiontext = option.innerText || option.textContent
|
||||
this.selected[option.value] = { id: option.value, name: optiontext, disabled: option.disabled };
|
||||
}
|
||||
@ -378,7 +402,7 @@ user_selector.prototype.output_options = function(data) {
|
||||
}
|
||||
|
||||
// If there was only one option matching the search results, select it.
|
||||
if (this.onlyoption && !this.onlyoption.disabled) {
|
||||
if (this.get_option('autoselectunique') && this.onlyoption && !this.onlyoption.disabled) {
|
||||
this.onlyoption.selected = true;
|
||||
if (!this.listbox.multiple) {
|
||||
this.selected = {};
|
||||
@ -441,4 +465,23 @@ user_selector.prototype.output_group = function(groupname, users, select) {
|
||||
}
|
||||
|
||||
// Say that we want to be a source of custom events.
|
||||
YAHOO.lang.augmentProto(user_selector, YAHOO.util.EventProvider);
|
||||
YAHOO.lang.augmentProto(user_selector, YAHOO.util.EventProvider);
|
||||
|
||||
/**
|
||||
* Initialise a class that updates the user's preferences when they change one of
|
||||
* the options checkboxes.
|
||||
* @constructor
|
||||
*/
|
||||
function user_selector_options_tracker() {
|
||||
var oself = this;
|
||||
YAHOO.util.Event.addListener('userselector_preserveselected', "change",
|
||||
function(e) { oself.handle_option_change('userselector_preserveselected') });
|
||||
YAHOO.util.Event.addListener('userselector_autoselectunique', "change",
|
||||
function(e) { oself.handle_option_change('userselector_autoselectunique') });
|
||||
YAHOO.util.Event.addListener('userselector_searchanywhere', "change",
|
||||
function(e) { oself.handle_option_change('userselector_searchanywhere') });
|
||||
}
|
||||
|
||||
user_selector_options_tracker.prototype.handle_option_change = function(option) {
|
||||
set_user_preference(option, document.getElementById(option).checked);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user