Skip to main content

Dropdowns

A reusable dropdown component

MDL-78279
4.3

How it works

Moodle dropdowns are output components to generate elements that expand extra floating information when clicked.

Currently, the core comes with two prebuild dropdowns:

  • Dropdown dialog: to display rich content inside the dropdown area.
  • Dropdown status: to display a list of available statuses

Source files

  • lib/classes/output/local/dialog.php: to define a dropdown dialog.
  • lib/classes/output/local/dialog.php: to define a dropdown dialog.
  • lib/classes/output/choicelist.php: generic output class to define a user choice.
  • lib/templates/local/dropdown/dialog.mustache
  • lib/templates/local/dropdown/status.mustache

Usage

The constructor for the dropdown dialog class only requires three parameters.

  • The button content
  • The dropdown content
  • An array of additional definitions. However, the output public methods can override all the definition values once the instance is created. Check out the examples to learn how to use them

The following example is the most simple example of creating a dropdown:

<?php 
$dialog = new core\output\local\dropdown\dialog('Open dialog button', 'Dialog content');
echo $OUTPUT->render($dialog);
 ?>

You have the option to include additional classes to the main component but also to the button itself.

<?php 
$dialog = new core\output\local\dropdown\dialog(
    'Open dialog',
    'Dialog content',
    [
        'classes' => 'mb-4',
        'buttonclasses' => 'btn btn-primary extraclass',
    ]
);
echo $OUTPUT->render($dialog);
 ?>
{ "buttonid" : "example01", "buttoncontent" : "Open dialog", "dialogcontent" : "Dialog content", "buttonclasses": "btn btn-primary extraclass" }
core/local/dropdown/dialog

If a specific item is floating towards the end of the page, you might consider aligning the dropdown menu to the left rather than to the right. To achieve this, you can use the POSITION constant values to set the dropdownposition $definition attribute or input it into the set_position method.

<?php 
$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content');
$dialog->set_position(core\output\local\dropdown\dialog::POSITION['end']);
echo $OUTPUT->render($dialog);
 ?>

By default, the dropdown width will adapt to the content. However, for long texts, there may be better scenarios. You can use the WIDTH constant values to set the dialogwidth $definition attribute or input it into the set_dialog_width method.

<?php 
// Big but fixed-width example.
$dialog = new core\output\local\dropdown\dialog('Big dialog', $content);
$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['big']);
echo $OUTPUT->render($dialog);

// Small width example.
$dialog = new core\output\local\dropdown\dialog('Small dialog', $content);
$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['small']);
echo $OUTPUT->render($dialog);
 ?>
{ "buttonid" : "example02", "buttoncontent" : "Big dialog", "dialogcontent" : "This is a long content for a big dialog that will be displayed in a fixed-width container.", "buttonclasses": "btn btn-primary extraclass", "dialogclasses": "dialog-big" }
core/local/dropdown/dialog

The dropdown status is a user-choice wrapper. To create it, first, you need to create an instance of core\output\choicelist that will be used to generate the dropdown content data.

<?php 
$choice = new core\output\choicelist('Dialog content');
$choice->add_option('option1', 'Option 1');
$choice->add_option('option2', 'Option 2');
$choice->add_option('option3', 'Option 3');
$choice->set_selected_value('option2');

$dialog = new core\output\local\dropdown\status('Open dialog button', $choice);
echo $OUTPUT->render($dialog);
 ?>
{ "buttonid" : "example04", "buttoncontent" : "Open dialog button", "dialogcontent" : "Dialog content", "choices" : { "hasoptions" : true, "options" : [ { "optionid" : "option1", "value" : "option1", "name" : "Option 1", "hasicon" : false, "first" : true, "optionnumber" : 1, "optionuniqid" : "option1uniqid" }, { "optionid" : "option2", "value" : "option2", "name" : "Option 2", "hasicon" : false, "selected" : true, "optionnumber" : 2, "optionuniqid" : "option2uniqid" }, { "optionid" : "option3", "value" : "option3", "name" : "Option 3", "hasicon" : false, "optionnumber" : 3, "optionuniqid" : "option3uniqid" } ] } }
core/local/dropdown/status

The status dropdown is an extension of the dropdown dialog, which means that all the definitions mentioned earlier can also be applied to it.

  • The status dropdown is also compatible with all the core\output\choicelist extra features like:
  • Adding additional icons and descriptions to the options
  • Disable options
  • Add links to options

The following example shows how to use the advanced features:

<?php 
$choice = new core\output\choicelist('Dialog content');

// Option one is a link.
$choice->add_option('option1', 'Option 1', [
    'url' => new moodle_url('/'),
]);
// Option two has an icon and description.
$choice->add_option('option2', 'Option 2', [
    'description' => 'Option 2 description',
    'icon' => new pix_icon('t/hide', 'Eye icon 2')
]);
// Option three is disabled.
$choice->add_option('option3', 'Option 3', [
    'disabled' => true,
]);

$choice->set_selected_value('option2');

$dialog = new core\output\local\dropdown\status('Open dialog button', $choice);
echo $OUTPUT->render($dialog);
 ?>

Sync button text with selected status

The status dropdown can be configured to sync the button text with the selected status.

To do so, you need to set the buttonsync $definition attribute to true.

<?php 
$choice = new core\output\choicelist();
$choice->add_option('option1', get_string('option1', YOURPLUGIN));
$choice->add_option('option2', get_string('option2', YOURPLUGIN));
$choice->set_selected_value('option2');

// Add some attribute to select through a query selector.
$dialog = new core\output\local\dropdown\status(
    get_string('buttontext', YOURPLUGIN),
    $choice,
    [
        'extras' => ['id' => 'mydropdown'],
        'buttonsync' => true,
        // With 'updatestatus' it will change the status when the user clicks an option
        // See "Dropdown status in update mode" section for more information.
        'updatestatus' => true,
    ]
);
echo $OUTPUT->render($dialog);
 ?>

Javascript

Controlling dropdowns

Both core/local/dropdown/status and core/local/dropdown/status AMD modules provide functions to:

  • Open and close the dropdown.
  • Change the button content.
  • Get the main dropdown HTML element.

Both modules are object-oriented. To get the dropdown instance, the process is as follows:

  1. Add id or data attributes to the main component to select it using a query selector.
  2. Import getDropdownDialog from core/local/dropdown/dialog, or getDropdownStatus from core/local/dropdown/status, depending on whether you use a dialogue or a status dropdown.
  3. Call getDropdownDialog or getDropdownStatus with the query selector to get the instance

Both classes provide the following methods:

  • setVisible(Boolean) to open or close the dropdown.
  • isVisible() to know if it is open or closed.
  • setButtonContent(String) to replace the button content.
  • getElement()to get the main HTMLElement to add eventListeners.

The following example uses the module to open the dropdown when an extra button is preset:

import {getDropdownDialog} from 'core/local/dropdown/';

const dialog = getDropdownDialog('[MYDROPDOWNSELECTOR]');
document.querySelector('[data-for="openDropdown"]').addEventListener('click', (event) => {
    event.stopPropagation();
    dialog.setVisible(true);
});

Specific dropdown status methods

The core/local/dropdown/status provides extra controls for the status selector, such as:

  • getSelectedValue() and setSelectedValue(String) to control the currently selected status.
  • isButtonSyncEnabled() and setButtonSyncEnabled(Boolean) to synchronise the button text with the selected status.
  • isUpdateStatusEnabled() and setUpdateStatusEnabled(Boolean) to control the auto-update status mode.

Using dropdown status from the frontend

The dropdown status can operate in two different ways.

The display-only is the default behaviour for any dropdown. In display-only mode, the component will show all the status values to the user, but it won’t handle and click the event nor change the current status.

If a plugin wants to change the status value when the user clicks, it should code a custom module to:

  1. Capture click event listeners to the choice items.
  2. Send the new status to the backend (using an ad-hoc webservice).
  3. If the webservice execution is ok, update the component value using the setSelectedValue instance method.

The following example shows how to render a display-only dropdown status in the backend:

<?php 
$choice = new core\output\choicelist('Dialog content');

// Add some data attributes to the choices.
$choice->add_option(
    'option1',
    get_string('option1', YOURPLUGIN), [
    extras' => ['data-action' => 'updateActionName']
]);
$choice->add_option(
    'option2',
    get_string('option2', YOURPLUGIN), [
    extras' => ['data-action' => 'updateActionName']
]);
$choice->set_selected_value('option2');

// Add some attribute to select through a query selector.
$dialog = new core\output\local\dropdown\status(
    get_string('buttontext', YOURPLUGIN),
    $choice,
    ['extras' => ['id' => 'mydropdown']]
);
echo $OUTPUT->render($dialog);
 ?>

Having this PHP code, the AMD controller could be something like:

import {getDropdownStatus} from 'core/local/dropdown/status';
import {sendValueToTheBackend} from 'YOURPLUGIN/example';

const status = getDropdownStatus('#mydropdown');
status.getElement().addEventListener('click', (event) => {
    const option = event.target.closest("[data-action='updateActionName']");
    if (!option) {
        return;
    }
    try {
        if(sendValueToTheBackend(option.dataset.value)) {
             status.setSelectedValue(option.dataset.value);
        }
    } catch (error) {
        // Do some error handling here.
    }
});

The component will act more like an HTML radio button in update mode. It will store the current status value and will trigger change events when the value changes.

In this case, the plugin controller has to:

  1. Capture the component element change event. Remember that, as in radio events, the change event won’t bubble, so it cannot be delegated to a parent element.
  2. Send the new status to the backend (using an ad-hoc webservice).
  3. If the webservice execution fails, do a value rollback using the setSelectedValue instance method.

The following example shows how to render an update mode dropdown status in the backend:

<?php 
$choice = new core\output\choicelist('Dialog content');

$choice->add_option('option1', get_string('option1', YOURPLUGIN));
$choice->add_option('option2', get_string('option2', YOURPLUGIN));
$choice->set_selected_value('option2');

// Add some attribute to select through a query selector.
$dialog = new core\output\local\dropdown\status(
    get_string('buttontext', YOURPLUGIN),
    $choice,
    [
        'extras' => ['id' => 'mydropdown'],
        'updatestatus' => true,
    ]
);
echo $OUTPUT->render($dialog);
 ?>

Having this PHP code, the AMD controller could be something like:

import {getDropdownStatus} from 'core/local/dropdown/status';
import {sendValueToTheBackend} from 'YOURPLUGIN/example';

const status = getDropdownStatus('#mydropdown');
let currentValue = status.getSelectedValue();

status.getElement().addEventListener('change', (event) => {
    if (currentValue == status.getSelectedValue()) {
        return;
    }
    try {
        sendValueToTheBackend(status.getSelectedValue());
        currentValue = status.getSelectedValue();
    } catch (error) {
        status.setSelectedValue(currentValue);
    }
});

Note: the event.target is also the main element. You can also get the current value from event.target.dataset.value if you prefer.

Examples