$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';
$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';
// 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;
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();});
}
/**
*/
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;
}
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*/, ' ');
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';
+ }
+}
\ No newline at end of file
* @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;
color: inherit;
text-decoration: none;
}
+.jsenabled .collapsed .collapsibleregioninner {
+ visibility: hidden;
+}
.noticebox {
border-width:1px;
.userselector div label {
margin-right: 0.3em;
}
-
+#userselector_options {
+ font-size: 0.75em;
+}
+#userselector_options .collapsibleregioncaption {
+ font-weight: bold;
+}
/***
*** Forms
***/
width: 100%;
}
.roleassigntable td {
- vertical-align: middle;
+ vertical-align: top;
padding: 0.2em 0.3em;
}
.roleassigntable p {
font-weight: bold;
}
.roleassigntable #buttonscell #addcontrols {
+ margin-top: 3em;
height: 13em;
}
.roleassigntable #removeselect_wrapper,
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 ==============================================================
/**
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);
}
/**
$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) {
$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) . ')';
}
// 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();
}
// 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);
}
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 = '';
// 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;
}
}
* @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);
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');
* @type Number
* @default 0.2
*/
-user_selector.prototype.querydelay = 0.2;
+user_selector.prototype.querydelay = 0.5;
// Internal fields =============================================================
// 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() == '';
}
/**
- * 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);
+ }
}
/**
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
// 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 };
}
}
// 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 = {};
}
// Say that we want to be a source of custom events.
-YAHOO.lang.augmentProto(user_selector, YAHOO.util.EventProvider);
\ No newline at end of file
+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);
+}
\ No newline at end of file