Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Ajax user selector

From MoodleDocs

Moodle 2.0

This was a proposal for a new user selector to be used on the assign roles and manage group memebers page, and also in two new reports I am writing for Moodle 2.0 to do with displaying a user's roles and permissions.

It is now finished, and you can see a screen-shot on the Roles administration improvements for Moodle 2.0 page. At some point this page should probably be converted into a docs page for people who want to use the new code. However, for now, I will just say that the code in user/selector/lib.php is well commented, and you can see how it is used in Moodle 2.0, so it should be easy to work out.

The aim is to improve usability compared to the existing interface.

A variant of this selector will be used for both the users already assigned the role, and the potential users. This should improve manageability of users in very large courses.

New configuration options

There will be an new admin option to control which fields are searched when selecting users, in addition to fullname. Three other options will be available: email, idnumber and username.

I know Petr will disapprove of username appearing on the list, because revealing usernames has security implications. However, I know several institutions that want this functionality, and I think we should supply it rather than making lots of different people hack the code. I will, however add a suitable warning to the description of this option. By default, only email will be selected, as at present.--Tim Hunt

User interface

The selector will be available either as a stand alone HTML control, or as a formslib component.

There will be options for selecting one or many users.

Moodle 1.9 user selector

With JavaScript off

It will work very much like it does at the moment:

It will be a list box that either lists users, or says that the list is too long and you should search. There will be a search box below with a search button to do the searching.

The list of users may be displayed in opt-groups, depending on where this control appears. For example, on the group members page, users are grouped by role.

There will always be at least one opt group, so the "7 potential users" label in the example would be changed to 'Potential users', and instead there will be an optgroup "All potential users (7)". This might change to "Users matching 'Fred' (4)".

With JavaScript on

As with JavaScript off, but the search button will disappear, and user searching will be done with Ajax.

When some users have already been selected, and the search text is changed so that one of the already selected users no longer matches the search term, then a special extra optgroup 'Already selected users' will be added at the start of the list, containing those users, still selected.

If we are trying to just select one user, and the text in the search box does only match one user, then that user is automatically selected.

Note that even with JavaScript turned on, the button click that actually does the the role assignment, or makes someone a member of a group, will still be a normal form submission.

HTML element names

Suppose that the developers asks for a user selector with name myuserselector. Then in the list box (<select>) will have name="myuserselector[]" or name="myuserselector" depending on whether we are doing multiple or single selection. The search text box will have name="myuserselector_searchtext" and the search button name="myuserselector_dosearch".

Code architecture

The code for a user selector will be wrapped up in a class. There will be a base class user_selector_base which does the common work. Subclasses will just be responsible for implementing the method that searches the database and a constructor. They could also provide other methods for setting options, if they wanted.

There will also be a formslib form element type.

Security

One issue with ajax interfaces is how you check permissions in the context of an ajax request, which often gives you very little context at all. Therefore, when a user_selector is displayed, a $selectorid hash code will be generated depending on the subclass name, the control name, and any options. At the same time, we will store the information about this user_selector in the session:

$USER->userselectors[$hash] = array(
    'class' = 'my_user_selector',
    // Probably some other values storing the options.
);

Then the ajax requests will go to a php file passing options ?selectorid={selectorid}&sesskey='{sesskey}'.

API

For users

require_once($CFG->dirroot);
// ...

$userselector = new group_memebers_user_selector('myuserselector', /* some options */);
$userselector->exclude($arrayofuserids); // For example when we are assigning roles, and don't want to list people who already have that role.
// Perhaps call some methods on $userselector to set more options? ...
// ...

if (data_submitted()) {
    // ...
    $users = $userselector->get_selected_users(); // Like optional_param('myuserselector', array(), PARAM_INTEGER), but with more validation.
    // ...
}

// ...
$userselector->display();
// ...

Usage within a Moodle form will involve creating a 'userselector' form field with some options in the usual sort of way.

For subclasses

class my_user_selector extends user_selector_base {
    public function __construct($name, /* custom options */) {
        parent::__construct($name);
        // Other stuff ...
    }

    protected function find_users() {
        $sql = 'SELECT ' . $this->required_fields_sql('u') . ', at.fieldwewant ' .
                'FROM {user} u JOIN {another_table} at ON at.userid = u.id ' .
                'WHERE ' . $this->search_sql('u') . ' AND at.field = ? ';
        $users = get_recordset_sql($sql, array(/* params */));
        $groupedusers = array();
        foreach ($users as $user) {
            $optgroup = // ...; // This will be the actual string that is displayed as the name of the optgroup.
            $groupedusers[$optgroup][] = $user;
        }
        return $groupedusers;
    }

    protected function get_options() {
        $options = parent::get_options();
        $options['file'] = 'pathforthisfile'; // ajax search requires this file from options
        // Add our custom options to the $options array.
        return $options;
    }
}

JavaScript

I don't think anything in YUI does quite what we want, so there will be some hand-coded JavaScript for populating the list box. However, I will try to use existing YUI code, for example the datasource, wherever possible. The JavaScript will just place a single user_selector_6534537 object in the global scope, where 6534537 is the hash mentioned under security above.

Where the code lives

I propose to put the code in a new user/selector folder. The code will consist of

lib.php
containing all the standard selector classes
script.js
the JavaScript
search.php
the target of ajax requests

There will also be a lib/form/userselector.php for the formslib element that wraps a userselector.

See also