Merge branch 'MDL-78279-master' of https://github.com/ferranrecio/moodle
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
---
|
||||
layout: docs
|
||||
title: "Dropdowns"
|
||||
description: "A reusable dropdown component"
|
||||
date: 2023-06-13T10:10:00+08:00
|
||||
draft: false
|
||||
tags:
|
||||
- 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
|
||||
|
||||
### Dropdown dialog
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
{{< mustache template="core/local/dropdown/dialog" >}}
|
||||
{
|
||||
"buttonid" : "example01",
|
||||
"buttoncontent" : "Open dialog",
|
||||
"dialogcontent" : "Dialog content",
|
||||
"buttonclasses": "btn btn-primary extraclass"
|
||||
}
|
||||
{{< /mustache >}}
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
{{< mustache template="core/local/dropdown/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"
|
||||
}
|
||||
{{< /mustache >}}
|
||||
|
||||
### Dropdown status
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
{{< mustache template="core/local/dropdown/status" >}}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{{< /mustache >}}
|
||||
|
||||
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);
|
||||
{{< / php >}}
|
||||
|
||||
## Capturing events with Javascript
|
||||
|
||||
Unfortunately, the current implementation does not yet include an ADM module for rendering or controlling dropdowns. However, you can create ad-hoc modules by adding id and data attributes to the relevant elements.
|
||||
|
||||
Here is an example of how to include id and data attributes on the main component:
|
||||
|
||||
{{< php >}}
|
||||
$dialog = new core\output\local\dropdown\dialog(
|
||||
'Open dialog',
|
||||
'Dialog content',
|
||||
[
|
||||
extras' => ['id' => 'mydropdown', 'data-foo' => 'bar']
|
||||
]
|
||||
);
|
||||
echo $OUTPUT->render($dialog);
|
||||
{{< / php >}}
|
||||
|
||||
Below is an example of how to include additional attributes to the options provided to the user:
|
||||
|
||||
{{< php >}}
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
|
||||
$choice->add_option('option1', 'Option 1', [
|
||||
extras' => ['id' => 'myoption1', 'data-foo' => 'bar1']
|
||||
]);
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
extras' => ['id' => 'myoption2', 'data-foo' => 'bar2']
|
||||
]);
|
||||
|
||||
$choice->set_selected_value('option2');
|
||||
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog button', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
{{< / php >}}
|
||||
|
||||
## Examples
|
||||
|
||||
<iframe src="../../../../examples/dropdowns.php" style="overflow:hidden;height:400px;width:100%;border:0" title="Moodle dynamic tabs"></iframe>
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle Component Library
|
||||
*
|
||||
* A sample page with dropdowns.
|
||||
*
|
||||
* @package tool_componentlibrary
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once(__DIR__ . '/../../../../config.php');
|
||||
|
||||
require_login();
|
||||
require_capability('moodle/site:configview', context_system::instance());
|
||||
|
||||
global $PAGE, $OUTPUT;
|
||||
$PAGE->set_url(new moodle_url('/admin/tool/componentlibrary/examples/dropdowns.php'));
|
||||
$PAGE->set_context(context_system::instance());
|
||||
$PAGE->set_pagelayout('embedded');
|
||||
|
||||
$PAGE->set_heading('Moodle dropdowns');
|
||||
$PAGE->set_title('Moodle dropdowns');
|
||||
|
||||
/** @var core_renderer $output*/
|
||||
$output = $PAGE->get_renderer('core');
|
||||
|
||||
echo $output->header();
|
||||
|
||||
echo $output->heading("Dropdown dialog example", 3);
|
||||
|
||||
$dialog = new core\output\local\dropdown\dialog(
|
||||
'Open dialog',
|
||||
'<p>Some rich content <b>element</b>.</p>
|
||||
<ul>
|
||||
<li>Item 1 <a href="#">Link 1</a></li>
|
||||
<li>Item 2 <a href="#">Link 2</a></li>
|
||||
</ul>'
|
||||
);
|
||||
echo $OUTPUT->render($dialog);
|
||||
|
||||
echo $output->heading("Dropdown status example", 3);
|
||||
|
||||
$choice = new core\output\choicelist('Choice description text');
|
||||
|
||||
// Option one is a link.
|
||||
$choice->add_option('option1', 'Option 1', [
|
||||
'description' => 'Option 1 description',
|
||||
'icon' => new pix_icon('t/show', 'Eye icon 1'),
|
||||
]);
|
||||
// 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', [
|
||||
'description' => 'Option 3 description',
|
||||
'icon' => new pix_icon('t/stealth', 'Eye icon 3'),
|
||||
]);
|
||||
|
||||
$choice->set_selected_value('option2');
|
||||
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog button', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
|
||||
echo $output->footer();
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\output;
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* A generic user choice output class.
|
||||
*
|
||||
* This class can be used as a generic user choice data structure for any dropdown, modal, or any
|
||||
* other component that offers choices to the user.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class choicelist implements renderable {
|
||||
|
||||
/** @var object[] The user choices. */
|
||||
protected $options = [];
|
||||
|
||||
/** @var string the selected option. */
|
||||
protected $selected = null;
|
||||
|
||||
/** @var string the choice description. */
|
||||
protected $description = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $description the choice description.
|
||||
*/
|
||||
public function __construct(?string $description = null) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add option to the user choice.
|
||||
*
|
||||
* The definition object could contain the following keys:
|
||||
* - string description: the description of the option.
|
||||
* - \moodle_url url: the URL to link to.
|
||||
* - \pix_icon icon: the icon to display.
|
||||
* - bool disabled: whether the option is disabled.
|
||||
* - bool selected: whether the option is selected.
|
||||
* - array extras: an array of HTML attributes to add to the option (attribute => value).
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $name
|
||||
* @param array $definition an optional array of definition for the option.
|
||||
*/
|
||||
public function add_option(string $value, string $name, array $definition = []) {
|
||||
$option = [
|
||||
'value' => $value,
|
||||
'name' => $name,
|
||||
'description' => $definition['description'] ?? null,
|
||||
'url' => $definition['url'] ?? null,
|
||||
'icon' => $definition['icon'] ?? null,
|
||||
'disabled' => (!empty($definition['disabled'])) ? true : false,
|
||||
];
|
||||
if (!empty($definition['selected'])) {
|
||||
$this->selected = $value;
|
||||
}
|
||||
$this->options[$value] = $option;
|
||||
if (isset($definition['extras'])) {
|
||||
$this->set_option_extras($value, $definition['extras']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected option.
|
||||
*
|
||||
* @param string $value The value of the selected option.
|
||||
*/
|
||||
public function set_selected_value(string $value) {
|
||||
$this->selected = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected option.
|
||||
*
|
||||
* @return string|null The value of the selected option.
|
||||
*/
|
||||
public function get_selected_value(): ?string {
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the general choice description option.
|
||||
*
|
||||
* @param string $value the new description.
|
||||
*/
|
||||
public function set_description(string $value) {
|
||||
$this->description = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the choice description option.
|
||||
*
|
||||
* @return string|null the current description.
|
||||
*/
|
||||
public function get_description(): ?string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the option disabled.
|
||||
*
|
||||
* @param string $value The value of the option.
|
||||
* @param bool $disabled Whether the option is disabled.
|
||||
*/
|
||||
public function set_option_disabled(string $value, bool $disabled) {
|
||||
if (isset($this->options[$value])) {
|
||||
$this->options[$value]['disabled'] = $disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the option disabled.
|
||||
*
|
||||
* @param string $value The value of the option.
|
||||
* @param array $extras an array to add HTML attributes to the option (attribute => value).
|
||||
*/
|
||||
public function set_option_extras(string $value, array $extras) {
|
||||
if (!isset($this->options[$value])) {
|
||||
return;
|
||||
}
|
||||
$extrasattributes = [];
|
||||
foreach ($extras as $attribute => $attributevalue) {
|
||||
$extrasattributes[] = [
|
||||
'attribute' => $attribute,
|
||||
'value' => $attributevalue,
|
||||
];
|
||||
}
|
||||
$this->options[$value]['extras'] = $extrasattributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export for template.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
$options = [];
|
||||
foreach ($this->options as $option) {
|
||||
if (!empty($option['icon'])) {
|
||||
$option['icon'] = $option['icon']->export_for_template($output);
|
||||
}
|
||||
$option['hasicon'] = !empty($option['icon']);
|
||||
|
||||
if (!empty($option['url'])) {
|
||||
$option['url'] = $option['url']->out(true);
|
||||
}
|
||||
$option['hasurl'] = !empty($option['url']);
|
||||
|
||||
if ($option['value'] == $this->selected) {
|
||||
$option['selected'] = true;
|
||||
}
|
||||
|
||||
$option['optionnumber'] = count($options) + 1;
|
||||
$option['first'] = count($options) === 0;
|
||||
$option['optionuniqid'] = \html_writer::random_id('choice_option_');
|
||||
|
||||
$options[] = $option;
|
||||
}
|
||||
return [
|
||||
'description' => $this->description,
|
||||
'options' => $options,
|
||||
'hasoptions' => !empty($options),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\output\local\dropdown;
|
||||
|
||||
use core\output\named_templatable;
|
||||
use renderable;
|
||||
|
||||
/**
|
||||
* Class to render a dropdown dialog element.
|
||||
*
|
||||
* A dropdown dialog allows to render any arbitrary HTML into a dropdown elements triggered
|
||||
* by a button.
|
||||
*
|
||||
* @package core
|
||||
* @category output
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class dialog implements named_templatable, renderable {
|
||||
/** Dropdown dialog positions. */
|
||||
public const POSITION = [
|
||||
'start' => 'dropdown-menu-left',
|
||||
'end' => 'dropdown-menu-right',
|
||||
];
|
||||
|
||||
/** Dropdown dialog positions. */
|
||||
public const WIDTH = [
|
||||
'default' => '',
|
||||
'big' => 'dialog-big',
|
||||
'small' => 'dialog-small',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string content of dialog.
|
||||
*/
|
||||
protected $dialogcontent = '';
|
||||
|
||||
/**
|
||||
* @var bool if the footer should auto enable or not.
|
||||
*/
|
||||
protected $buttoncontent = true;
|
||||
|
||||
/**
|
||||
* @var string trigger button CSS classes.
|
||||
*/
|
||||
protected $buttonclasses = '';
|
||||
|
||||
/**
|
||||
* @var string component CSS classes.
|
||||
*/
|
||||
protected $classes = '';
|
||||
|
||||
/**
|
||||
* @var string the dropdown position.
|
||||
*/
|
||||
protected $dropdownposition = self::POSITION['start'];
|
||||
|
||||
/**
|
||||
* @var string dropdown preferred width.
|
||||
*/
|
||||
protected $dropdownwidth = self::WIDTH['default'];
|
||||
|
||||
|
||||
/**
|
||||
* @var array extra HTML attributes (attribute => value).
|
||||
*/
|
||||
protected $extras = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The definition object could contain the following keys:
|
||||
* - classes: component CSS classes.
|
||||
* - buttonclasses: the button CSS classes.
|
||||
* - dialogwidth: the dropdown width.
|
||||
* - dropdownposition: the dropdown position.
|
||||
* - extras: extra HTML attributes (attribute => value).
|
||||
*
|
||||
* @param string $buttoncontent the button content
|
||||
* @param string $dialogcontent the footer content
|
||||
* @param array $definition an optional array of the element definition
|
||||
*/
|
||||
public function __construct(string $buttoncontent, string $dialogcontent, array $definition = []) {
|
||||
$this->buttoncontent = $buttoncontent;
|
||||
$this->dialogcontent = $dialogcontent;
|
||||
if (isset($definition['classes'])) {
|
||||
$this->classes = $definition['classes'];
|
||||
}
|
||||
if (isset($definition['buttonclasses'])) {
|
||||
$this->buttonclasses = $definition['buttonclasses'];
|
||||
}
|
||||
if (isset($definition['extras'])) {
|
||||
$this->extras = $definition['extras'];
|
||||
}
|
||||
if (isset($definition['dialogwidth'])) {
|
||||
$this->dropdownwidth = $definition['dialogwidth'];
|
||||
}
|
||||
if (isset($definition['dropdownposition'])) {
|
||||
$this->dropdownposition = $definition['dropdownposition'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dialog contents.
|
||||
*
|
||||
* @param string $dialogcontent
|
||||
*/
|
||||
public function set_content(string $dialogcontent) {
|
||||
$this->dialogcontent = $dialogcontent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the button contents.
|
||||
*
|
||||
* @param string $buttoncontent
|
||||
* @param string|null $buttonclasses the button classes
|
||||
*/
|
||||
public function set_button(string $buttoncontent, ?string $buttonclasses = null) {
|
||||
$this->buttoncontent = $buttoncontent;
|
||||
if ($buttonclasses !== null) {
|
||||
$this->buttonclasses = $buttonclasses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dialog width.
|
||||
*
|
||||
* @param string $width
|
||||
*/
|
||||
public function set_dialog_width(string $width) {
|
||||
$this->dropdownwidth = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra classes to trigger butotn.
|
||||
*
|
||||
* @param string $buttonclasses the extra classes
|
||||
*/
|
||||
public function set_button_classes(string $buttonclasses) {
|
||||
$this->buttonclasses = $buttonclasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra classes to the component.
|
||||
*
|
||||
* @param string $classes the extra classes
|
||||
*/
|
||||
public function set_classes(string $classes) {
|
||||
$this->classes = $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra extras to the sticky footer element.
|
||||
*
|
||||
* @param string $attribute the extra attribute
|
||||
* @param string $value the value
|
||||
*/
|
||||
public function add_extra(string $attribute, string $value) {
|
||||
$this->extras[$attribute] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the button element id.
|
||||
*
|
||||
* @param string $value the value
|
||||
*/
|
||||
public function add_button_id(string $value) {
|
||||
$this->extras['id'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dropdown position.
|
||||
* @param string $position the position
|
||||
*/
|
||||
public function set_position(string $position) {
|
||||
$this->dropdownposition = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param \renderer_base $output typically, the renderer that's calling this function
|
||||
* @return array data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
$extras = [];
|
||||
foreach ($this->extras as $attribute => $value) {
|
||||
$extras[] = [
|
||||
'attribute' => $attribute,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
$data = [
|
||||
// Id is required for the correct HTML labelling.
|
||||
'buttonid' => \html_writer::random_id('dropwdownbutton_'),
|
||||
'buttoncontent' => (string) $this->buttoncontent,
|
||||
'dialogcontent' => (string) $this->dialogcontent,
|
||||
'classes' => $this->classes,
|
||||
'buttonclasses' => $this->buttonclasses,
|
||||
'dialogclasses' => $this->dropdownwidth,
|
||||
'extras' => $extras,
|
||||
];
|
||||
// Bootstrap 4 dropdown position still uses left and right literals.
|
||||
$data["position"] = $this->dropdownposition;
|
||||
if (right_to_left()) {
|
||||
$rltposition = [
|
||||
self::POSITION['start'] => self::POSITION['end'],
|
||||
self::POSITION['end'] => self::POSITION['end'],
|
||||
];
|
||||
$data["position"] = $rltposition[$this->dropdownposition];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the template to use for this templatable.
|
||||
*
|
||||
* @param \renderer_base $renderer The renderer requesting the template name
|
||||
* @return string the template name
|
||||
*/
|
||||
public function get_template_name(\renderer_base $renderer): string {
|
||||
return 'core/local/dropdown/dialog';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\output\local\dropdown;
|
||||
|
||||
use core\output\choicelist;
|
||||
|
||||
/**
|
||||
* Class to render a dropdown dialog element.
|
||||
*
|
||||
* A dropdown dialog allows to render any arbitrary HTML into a dropdown elements triggered
|
||||
* by a button.
|
||||
*
|
||||
* @package core
|
||||
* @category output
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class status extends dialog {
|
||||
|
||||
/**
|
||||
* @var choicelist content of dialog.
|
||||
*/
|
||||
protected $choices = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The definition object could contain the following keys:
|
||||
* - classes: component CSS classes.
|
||||
* - buttonclasses: the button CSS classes.
|
||||
* - dialogwidth: the dropdown width.
|
||||
* - extras: extra HTML attributes (attribute => value).
|
||||
*
|
||||
* @param string $buttoncontent the button content
|
||||
* @param choicelist $choices the choice object
|
||||
* @param array $definition an optional array of the element definition
|
||||
*/
|
||||
public function __construct(string $buttoncontent, choicelist $choices, array $definition = []) {
|
||||
parent::__construct($buttoncontent, '', $definition);
|
||||
$this->set_choice($choices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dialog contents.
|
||||
*
|
||||
* @param choicelist $choices
|
||||
*/
|
||||
public function set_choice(choicelist $choices) {
|
||||
$this->choices = $choices;
|
||||
$description = $choices->get_description();
|
||||
if (!empty($description)) {
|
||||
$this->set_content($description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param \renderer_base $output typically, the renderer that's calling this function
|
||||
* @return array data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
$data = parent::export_for_template($output);
|
||||
if ($this->choices !== null) {
|
||||
$data['choices'] = $this->choices->export_for_template($output);
|
||||
}
|
||||
$selectedvalue = $this->choices->get_selected_value();
|
||||
if ($selectedvalue !== null) {
|
||||
$data['extras'][] = (object)[
|
||||
'attribute' => 'data-value',
|
||||
'value' => $selectedvalue,
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the template to use for this templatable.
|
||||
*
|
||||
* @param \renderer_base $renderer The renderer requesting the template name
|
||||
* @return string the template name
|
||||
*/
|
||||
public function get_template_name(\renderer_base $renderer): string {
|
||||
return 'core/local/dropdown/status';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/local/dropdown/dialog
|
||||
|
||||
Displays a dropdown dialog component.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* buttoncontent String - the dropdown trigger button content.
|
||||
* dialogcontent String - the dropdown dialog content.
|
||||
|
||||
Optional blocks:
|
||||
* dropdownclasses - additional classes for the dropdown.
|
||||
* buttonclasses - additional classes for the dropdown trigger button.
|
||||
* dialogclasses - additional classes for the dropdown dialog.
|
||||
* dialogcontent - the dropdown dialog content.
|
||||
* buttoncontent - the dropdown trigger button content.
|
||||
* extras - custom HTML attributes for the component.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"buttonid" : "someinternalid",
|
||||
"buttoncontent" : "Trigger button",
|
||||
"dialogcontent" : "<a href=\"#\">Moodle</a>",
|
||||
"extras" : [
|
||||
{
|
||||
"attribute" : "data-example",
|
||||
"value" : "stickyfooter"
|
||||
}
|
||||
],
|
||||
"buttonclasses" : "extraclasses",
|
||||
"dialogclasses" : "extraclasses",
|
||||
"classes" : "extraclasses"
|
||||
}
|
||||
}}
|
||||
<div
|
||||
class="dropdown {{!
|
||||
}} {{$ dropdownclasses }} {{!
|
||||
}} {{#classes}} {{classes}} {{/classes}} {{!
|
||||
}} {{/ dropdownclasses }}"
|
||||
{{$ extras }}
|
||||
{{#extras}}
|
||||
{{attribute}}="{{value}}"
|
||||
{{/extras}}
|
||||
{{/ extras }}
|
||||
>
|
||||
<button
|
||||
class="{{$ buttonclasses }} {{!
|
||||
}} {{^buttonclasses}} btn btn-light btn-outline-secondary dropdown-toggle {{/buttonclasses}} {{!
|
||||
}} {{#buttonclasses}} {{buttonclasses}} {{/buttonclasses}} {{!
|
||||
}} {{/ buttonclasses }}"
|
||||
type="button"
|
||||
id="{{#buttonid}}{{buttonid}}{{/buttonid}}{{^buttonid}}dropdownDialog{{uniqid}}{{/buttonid}}"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{{$ buttoncontent }}
|
||||
{{{ buttoncontent }}}
|
||||
{{/ buttoncontent }}
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu {{!
|
||||
}} {{#position}} {{position}} {{/position}} {{!
|
||||
}} {{$ dialogclasses }} {{!
|
||||
}} {{#dialogclasses}} {{dialogclasses}} {{/dialogclasses}} {{!
|
||||
}} {{/ dialogclasses }}"
|
||||
aria-labelledby="{{#buttonid}}{{buttonid}}{{/buttonid}}{{^buttonid}}dropdownDialog{{uniqid}}{{/buttonid}}"
|
||||
>
|
||||
<div class="p-2" data-for="dropdowndialog_content">
|
||||
{{$ dialogcontent }}
|
||||
{{{dialogcontent}}}
|
||||
{{/ dialogcontent }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,153 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/local/dropdown/status
|
||||
|
||||
Displays a dropdown status selector component.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* buttoncontent String - the dropdown trigger button content.
|
||||
* choices Array - the status options.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"buttonid" : "someinternalid",
|
||||
"buttoncontent" : "Trigger button",
|
||||
"choices" : {
|
||||
"hasoptions": true,
|
||||
"dialogcontent": "Dialog content",
|
||||
"options": [
|
||||
{
|
||||
"optionid": "option1",
|
||||
"value": "value1",
|
||||
"name": "First option",
|
||||
"description": "First option description",
|
||||
"hasicon": false,
|
||||
"first": true,
|
||||
"optionnumber": 1,
|
||||
"optionuniqid": "option1uniqid",
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"optionid": "option2",
|
||||
"value": "value2",
|
||||
"name": "Second option",
|
||||
"description": "Second option description",
|
||||
"icon": {
|
||||
"extraclasses": "iconhelp",
|
||||
"attributes": [
|
||||
{"name": "src", "value": "../../../pix/help.svg"},
|
||||
{"name": "alt", "value": "Help icon"}
|
||||
]
|
||||
},
|
||||
"hasicon": true,
|
||||
"optionnumber": 2,
|
||||
"optionuniqid": "option2uniqid"
|
||||
},
|
||||
{
|
||||
"optionid": "option3",
|
||||
"value": "value3",
|
||||
"name": "Third option",
|
||||
"description": "Third option description",
|
||||
"icon": {
|
||||
"extraclasses": "iconhelp",
|
||||
"attributes": [
|
||||
{"name": "src", "value": "../../../pix/help.svg"},
|
||||
{"name": "alt", "value": "Help icon"}
|
||||
]
|
||||
},
|
||||
"hasicon": true,
|
||||
"disabled": true,
|
||||
"optionnumber": 3,
|
||||
"optionuniqid": "option3uniqid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"extras" : [
|
||||
{
|
||||
"attribute" : "data-example",
|
||||
"value" : "stickyfooter"
|
||||
}
|
||||
],
|
||||
"buttonclasses" : "extraclasses"
|
||||
}
|
||||
}}
|
||||
{{< core/local/dropdown/dialog }}
|
||||
{{$ dialogcontent }}
|
||||
{{#dialogcontent}}
|
||||
<div class="pb-2 border-bottom">{{{dialogcontent}}}</div>
|
||||
{{/dialogcontent}}
|
||||
{{#choices}}
|
||||
<div class="d-flex flex-column" role="listbox">
|
||||
{{#options}}
|
||||
<div
|
||||
class="d-flex flex-row align-items-start p-2 position-relative rounded {{!
|
||||
}} {{#disabled}} dimmed_text {{/disabled}} {{!
|
||||
}} {{#selected}} bg-light selected {{/selected}}"
|
||||
data-optionnumber="{{optionnumber}}"
|
||||
data-selected="{{selected}}"
|
||||
>
|
||||
{{#icon}}
|
||||
<div class="option-icon">
|
||||
{{>core/pix_icon}}
|
||||
</div>
|
||||
{{/icon}}
|
||||
<div class="otion-select-indicator">
|
||||
{{#selected}}
|
||||
{{#pix}} i/checkedcircle, core, {{#str}} selected, form {{/str}} {{/pix}}
|
||||
{{/selected}}
|
||||
{{^selected}}
|
||||
{{#pix}} i/uncheckedcircle{{/pix}}
|
||||
{{/selected}}
|
||||
</div>
|
||||
<div class="option-name">
|
||||
<a
|
||||
class="stretched-link {{!
|
||||
}} {{#disabled}} disabled {{/disabled}} {{!
|
||||
}} {{#selected}} selected disabled {{/selected}}"
|
||||
role="option"
|
||||
{{#selected}} aria-selected="true" {{/selected}}
|
||||
{{#description}} aria-describedby="{{optionuniqid}}" {{/description}}
|
||||
data-value="{{value}}"
|
||||
{{#hasurl}} href="{{{url}}}" {{/hasurl}}
|
||||
{{! If there is no url, supose JS will handle it somehow. }}
|
||||
{{^hasurl}} href="#" {{/hasurl}}
|
||||
{{#disabled}} tabindex="-1" {{/disabled}}
|
||||
{{#disabled}} aria-disabled="true" {{/disabled}}
|
||||
{{$extras}}
|
||||
{{#extras}}
|
||||
{{attribute}}="{{value}}"
|
||||
{{/extras}}
|
||||
{{/extras}}
|
||||
>
|
||||
{{name}}
|
||||
</a>
|
||||
{{#description}}
|
||||
<div id="{{optionuniqid}}" class="small text-muted">
|
||||
{{{description}}}
|
||||
</div>
|
||||
{{/description}}
|
||||
</div>
|
||||
</div>
|
||||
{{/options}}
|
||||
</div>
|
||||
{{/choices}}
|
||||
{{/ dialogcontent }}
|
||||
{{/ core/local/dropdown/dialog }}
|
||||
@@ -0,0 +1,64 @@
|
||||
@core @javascript
|
||||
Feature: Test dropdown output module
|
||||
In order to show extra information to the user
|
||||
As a user
|
||||
I need to interact with the dropdown output modules
|
||||
|
||||
Background:
|
||||
# Get to the fixture page.
|
||||
Given I log in as "admin"
|
||||
And I am on fixture page "/lib/tests/behat/fixtures/dropdown_output_testpage.php"
|
||||
And I should not see "Dialog content"
|
||||
|
||||
Scenario: User can open a dropdown dialog
|
||||
When I click on "Open dialog" "button" in the "regularscenario" "region"
|
||||
Then I should see "Dialog content" in the "regularscenario" "region"
|
||||
|
||||
Scenario: Dropdown dialog can have rich content inside
|
||||
When I click on "Open dialog" "button" in the "richcontent" "region"
|
||||
Then I should see "Some rich content" in the "richcontent" "region"
|
||||
And "Link 1" "link" should exist in the "richcontent" "region"
|
||||
And "Eye icon" "icon" should exist in the "richbutton" "region"
|
||||
|
||||
Scenario: HTML attributtes can be overriden in dropdowns
|
||||
When I click on "Open dialog" "button" in the "cssoverride" "region"
|
||||
Then I should see "Dialog content" in the "cssoverride" "region"
|
||||
And ".extraclass" "css_element" should exist in the "cssoverride" "region"
|
||||
And "[data-foo='bar']" "css_element" should exist in the "extraattributes" "region"
|
||||
And I should see "Custom ID button found" in the "customid" "region"
|
||||
And "#CustomDropdownButtonId" "css_element" should exist in the "customid" "region"
|
||||
And ".dialog-big" "css_element" should exist in the "widths" "region"
|
||||
And ".dialog-small" "css_element" should exist in the "widths" "region"
|
||||
|
||||
Scenario: User can open a dropdown status
|
||||
When I click on "Open dialog" "button" in the "statusregularscenario" "region"
|
||||
Then I should see "Option 1" in the "statusregularscenario" "region"
|
||||
And I should see "Option 1 description" in the "statusregularscenario" "region"
|
||||
And I should see "Option 2" in the "statusregularscenario" "region"
|
||||
And I should see "Option 2 description" in the "statusregularscenario" "region"
|
||||
And "Eye icon 1" "icon" should exist in the "statusregularscenario" "region"
|
||||
And "Eye icon 2" "icon" should exist in the "statusregularscenario" "region"
|
||||
|
||||
Scenario: Dropdown status can have as selected option
|
||||
When I click on "Open dialog" "button" in the "statusselectedscenario" "region"
|
||||
Then "Selected" "icon" should exist in the "#statusselectedscenario [data-optionnumber='2']" "css_element"
|
||||
And "Selected" "icon" should not exist in the "#statusselectedscenario [data-optionnumber='1']" "css_element"
|
||||
And "Selected" "icon" should not exist in the "#statusselectedscenario [data-optionnumber='3']" "css_element"
|
||||
|
||||
Scenario: Dropdown status can have a disabled option
|
||||
When I click on "Open dialog" "button" in the "statusdisablescenario" "region"
|
||||
Then ".disabled" "css_element" should exist in the "#statusdisablescenario [data-optionnumber='2']" "css_element"
|
||||
And ".disabled" "css_element" should not exist in the "#statusdisablescenario [data-optionnumber='1']" "css_element"
|
||||
And ".disabled" "css_element" should not exist in the "#statusdisablescenario [data-optionnumber='3']" "css_element"
|
||||
|
||||
Scenario: Dropdown status can have a extra attribute in the options
|
||||
When I click on "Open dialog" "button" in the "statusoptionextrasscenario" "region"
|
||||
Then "[data-foo='bar']" "css_element" should exist in the "#statusoptionextrasscenario [data-optionnumber='2']" "css_element"
|
||||
And "[data-foo='bar']" "css_element" should not exist in the "#statusoptionextrasscenario [data-optionnumber='1']" "css_element"
|
||||
And "[data-foo='bar']" "css_element" should not exist in the "#statusoptionextrasscenario [data-optionnumber='3']" "css_element"
|
||||
|
||||
Scenario: Dropdown status can define urls in options
|
||||
Given I should see "Foo param value: none"
|
||||
When I click on "Open dialog" "button" in the "statusoptionurl" "region"
|
||||
And I click on "Option 2" "link" in the "statusoptionurl" "region"
|
||||
Then I should see "Foo param value: bar"
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Test page for dropdown dialog output component.
|
||||
*
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @package core
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../../config.php');
|
||||
|
||||
defined('BEHAT_SITE_RUNNING') || die();
|
||||
|
||||
global $CFG, $PAGE, $OUTPUT;
|
||||
$PAGE->set_url('/lib/tests/behat/fixtures/dropdown_dialog_testpage.php');
|
||||
$PAGE->add_body_class('limitedwidth');
|
||||
require_login();
|
||||
$PAGE->set_context(core\context\system::instance());
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
echo "<h2>Dropdown dialog test page</h2>";
|
||||
|
||||
echo '<div id="regularscenario" class="mb-4">';
|
||||
echo "<h3>Basic example</h3>";
|
||||
$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content');
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="richcontent" class="mb-4">';
|
||||
echo "<h3>Rich content example</h3>";
|
||||
$content = '
|
||||
<p>Some rich content <b>element</b>.</p>
|
||||
<ul>
|
||||
<li>Item 1 <a href="#">Link 1</a></li>
|
||||
<li>Item 2 <a href="#">Link 2</a></li>
|
||||
</ul>
|
||||
';
|
||||
$dialog = new core\output\local\dropdown\dialog('Open dialog', $content);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="richbutton" class="mb-4">';
|
||||
echo "<h3>Rich button example</h3>";
|
||||
$button = $OUTPUT->pix_icon('t/hide', 'Eye icon') . ' Click to <b>open</b></a>';
|
||||
$dialog = new core\output\local\dropdown\dialog($button, 'Dialog content');
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="cssoverride" class="mb-4">';
|
||||
echo "<h3>CSS override example</h3>";
|
||||
$dialog = new core\output\local\dropdown\dialog(
|
||||
'Open dialog',
|
||||
'Dialog content',
|
||||
[
|
||||
'buttonclasses' => 'btn btn-primary extraclass',
|
||||
]
|
||||
);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="extraattributes" class="mb-4">';
|
||||
echo "<h3>Extra data attributes example</h3>";
|
||||
$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content', [
|
||||
'extras' => ['data-foo' => 'bar'],
|
||||
]);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="customid" class="mb-4">';
|
||||
echo "<h3>Custom element id</h3>";
|
||||
$dialog = new core\output\local\dropdown\dialog('Open dialog', 'Dialog content', [
|
||||
'extras' => ['id' => 'CustomDropdownButtonId'],
|
||||
]);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
$inlinejs = "document.querySelector('#CustomDropdownButtonId button').innerHTML = 'Custom ID button found';";
|
||||
$PAGE->requires->js_amd_inline($inlinejs);
|
||||
|
||||
echo '<div id="position" class="mb-4">';
|
||||
echo "<h3>Dropdown position example</h3>";
|
||||
$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);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="widths" class="mb-4">';
|
||||
echo "<h3>Dropdown max width values example</h3>";
|
||||
$content = '
|
||||
Some long long content. Some long long content. Some long long content. Some long long content.
|
||||
Some long long content. Some long long content. Some long long content. Some long long content.
|
||||
Some long long content. Some long long content. Some long long content. Some long long content.
|
||||
';
|
||||
|
||||
$dialog = new core\output\local\dropdown\dialog('Default dialog (adaptative)', $content);
|
||||
$dialog->set_classes('mb-3');
|
||||
echo $OUTPUT->render($dialog);
|
||||
|
||||
$dialog = new core\output\local\dropdown\dialog('Big dialog', $content);
|
||||
$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['big']);
|
||||
$dialog->set_classes('mb-3');
|
||||
echo $OUTPUT->render($dialog);
|
||||
|
||||
$dialog = new core\output\local\dropdown\dialog('Small dialog', $content);
|
||||
$dialog->set_dialog_width(core\output\local\dropdown\dialog::WIDTH['small']);
|
||||
$dialog->set_classes('mb-3');
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo "<h2>Dropdown status test page</h2>";
|
||||
|
||||
echo '<div id="statusregularscenario" class="mb-4">';
|
||||
echo "<h3>Basic example</h3>";
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
$choice->add_option('option1', 'Option 1', [
|
||||
'description' => 'Option 1 description'
|
||||
]);
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
'description' => 'Option 2 description',
|
||||
'icon' => new pix_icon('t/hide', 'Eye icon 1')
|
||||
]);
|
||||
$choice->add_option('option3', 'Option 3', [
|
||||
'icon' => new pix_icon('t/show', 'Eye icon 2')
|
||||
]);
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="statusselectedscenario" class="mb-4">';
|
||||
echo "<h3>Selected element example</h3>";
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
$choice->add_option('option1', 'Option 1', [
|
||||
'description' => 'Option 1 description',
|
||||
'icon' => new pix_icon('t/show', 'Eye icon 1')
|
||||
]);
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
'description' => 'Option 2 description',
|
||||
'icon' => new pix_icon('t/hide', 'Eye icon 2')
|
||||
]);
|
||||
$choice->add_option('option3', 'Option 3', [
|
||||
'description' => 'Option 3 description',
|
||||
'icon' => new pix_icon('t/stealth', 'Eye icon 3')
|
||||
]);
|
||||
$choice->set_selected_value('option2');
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="statusdisablescenario" class="mb-4">';
|
||||
echo "<h3>Disable option example</h3>";
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
$choice->add_option('option1', 'Option 1');
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
'description' => 'Option 2 description',
|
||||
'icon' => new pix_icon('t/hide', 'Eye icon')
|
||||
]);
|
||||
$choice->add_option('option3', 'Option 3');
|
||||
$choice->set_option_disabled('option2', true);
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="statusoptionextrasscenario" class="mb-4">';
|
||||
echo "<h3>Set option extra attributes example</h3>";
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
$choice->add_option('option1', 'Option 1');
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
'description' => 'Option 2 description',
|
||||
'icon' => new pix_icon('t/hide', 'Eye icon')
|
||||
]);
|
||||
$choice->add_option('option3', 'Option 3');
|
||||
$choice->set_option_extras('option2', ['data-foo' => 'bar']);
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
echo '</div>';
|
||||
|
||||
echo '<div id="statusoptionurl" class="mb-4">';
|
||||
echo "<h3>Set option url example</h3>";
|
||||
$choice = new core\output\choicelist('Dialog content');
|
||||
$choice->add_option('option1', 'Option 1');
|
||||
$choice->add_option('option2', 'Option 2', [
|
||||
'url' => new moodle_url('/lib/tests/behat/fixtures/dropdown_output_testpage.php', ['foo' => 'bar']),
|
||||
]);
|
||||
$choice->add_option('option3', 'Option 3');
|
||||
$choice->set_option_extras('option2', ['data-foo' => 'bar']);
|
||||
$dialog = new core\output\local\dropdown\status('Open dialog', $choice);
|
||||
echo $OUTPUT->render($dialog);
|
||||
$foo = optional_param('foo', 'none', PARAM_TEXT);
|
||||
echo "<p>Foo param value: $foo</p>";
|
||||
echo '</div>';
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for core\output\choice class.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2023 Ferran Recio <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\output;
|
||||
|
||||
use advanced_testcase;
|
||||
|
||||
/**
|
||||
* Unit tests for the `icon_system` class.
|
||||
*
|
||||
* @coversDefaultClass \core\output\choicelist
|
||||
*/
|
||||
class choicelist_test extends advanced_testcase {
|
||||
/**
|
||||
* Test for a choice without options.
|
||||
*
|
||||
* @covers ::_construct
|
||||
* @covers ::add_option
|
||||
* @covers ::export_for_template
|
||||
*/
|
||||
public function test_empty_export(): void {
|
||||
$page = new \moodle_page();
|
||||
$page->set_url('/user/profile.php');
|
||||
$page->set_context(\context_system::instance());
|
||||
$renderer = $page->get_renderer('core');
|
||||
|
||||
$choice = new choicelist('Choose an option');
|
||||
$export = $choice->export_for_template($renderer);
|
||||
|
||||
$this->assertEquals('Choose an option', $export['description']);
|
||||
$this->assertEquals(false, $export['hasoptions']);
|
||||
$this->assertEquals([], $export['options']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for a choice with basic options.
|
||||
*
|
||||
* @covers ::_construct
|
||||
* @covers ::add_option
|
||||
* @covers ::export_for_template
|
||||
*/
|
||||
public function test_basic_export(): void {
|
||||
$page = new \moodle_page();
|
||||
$page->set_url('/user/profile.php');
|
||||
$page->set_context(\context_system::instance());
|
||||
$renderer = $page->get_renderer('core');
|
||||
|
||||
$choice = new choicelist('Choose an option');
|
||||
$choice->add_option('option1', 'Option 1');
|
||||
$choice->add_option('option2', 'Option 2');
|
||||
|
||||
$export = $choice->export_for_template($renderer);
|
||||
|
||||
$this->assertEquals('Choose an option', $export['description']);
|
||||
$this->assertEquals(true, $export['hasoptions']);
|
||||
$this->assertCount(2, $export['options']);
|
||||
$this->validate_option($export['options'][0], 'option1', 'Option 1', []);
|
||||
$this->validate_option($export['options'][1], 'option2', 'Option 2', []);
|
||||
}
|
||||
/**
|
||||
* Test for a choice with extras options definition.
|
||||
*
|
||||
* @covers ::_construct
|
||||
* @covers ::add_option
|
||||
* @covers ::set_option_extras
|
||||
* @covers ::export_for_template
|
||||
*/
|
||||
public function test_option_defintion_export(): void {
|
||||
$page = new \moodle_page();
|
||||
$page->set_url('/user/profile.php');
|
||||
$page->set_context(\context_system::instance());
|
||||
$renderer = $page->get_renderer('core');
|
||||
|
||||
$choice = new choicelist('Choose an option');
|
||||
$definition1 = [
|
||||
'disabled' => true,
|
||||
'description' => 'Description',
|
||||
'url' => new \moodle_url('/user/profile.php'),
|
||||
'icon' => new \pix_icon('i/grade', 'Grade'),
|
||||
'extras' => [
|
||||
'data-attribute' => 'value',
|
||||
],
|
||||
];
|
||||
$choice->add_option('option1', 'Option 1', $definition1);
|
||||
$definition2 = [
|
||||
'disabled' => false,
|
||||
'description' => null,
|
||||
'url' => null,
|
||||
'icon' => null,
|
||||
'extras' => null,
|
||||
];
|
||||
$choice->add_option('option2', 'Option 2', $definition2);
|
||||
|
||||
$export = $choice->export_for_template($renderer);
|
||||
|
||||
$this->assertEquals('Choose an option', $export['description']);
|
||||
$this->assertEquals(true, $export['hasoptions']);
|
||||
$this->assertCount(2, $export['options']);
|
||||
$definition1['iconexport'] = $definition1['icon']->export_for_template($renderer);
|
||||
$this->validate_option($export['options'][0], 'option1', 'Option 1', $definition1);
|
||||
$this->validate_option($export['options'][1], 'option2', 'Option 2', $definition2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for a choice with option selected.
|
||||
*
|
||||
* @covers ::_construct
|
||||
* @covers ::add_option
|
||||
* @covers ::set_selected_value
|
||||
* @covers ::get_selected_value
|
||||
* @covers ::export_for_template
|
||||
*/
|
||||
public function test_option_selected_export(): void {
|
||||
$page = new \moodle_page();
|
||||
$page->set_url('/user/profile.php');
|
||||
$page->set_context(\context_system::instance());
|
||||
$renderer = $page->get_renderer('core');
|
||||
|
||||
$choice = new choicelist('Choose an option');
|
||||
$choice->add_option('option1', 'Option 1');
|
||||
$choice->add_option('option2', 'Option 2');
|
||||
$choice->set_selected_value('option1');
|
||||
|
||||
$export = $choice->export_for_template($renderer);
|
||||
|
||||
$this->assertEquals('Choose an option', $export['description']);
|
||||
$this->assertEquals(true, $export['hasoptions']);
|
||||
$this->assertCount(2, $export['options']);
|
||||
$this->assertEquals('option1', $choice->get_selected_value());
|
||||
$this->validate_option($export['options'][0], 'option1', 'Option 1', [], true);
|
||||
$this->validate_option($export['options'][1], 'option2', 'Option 2', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a choice option export.
|
||||
* @param array $option the option export
|
||||
* @param string $value the option value
|
||||
* @param string $name the option name
|
||||
* @param array|null $definition the option definition
|
||||
* @param bool $selected if the option is selected
|
||||
*/
|
||||
private function validate_option(
|
||||
array $option,
|
||||
string $value,
|
||||
string $name,
|
||||
?array $definition,
|
||||
bool $selected = false
|
||||
): void {
|
||||
$this->assertEquals($value, $option['value']);
|
||||
$this->assertEquals($name, $option['name']);
|
||||
$this->assertEquals($definition['disabled'] ?? false, $option['disabled']);
|
||||
$this->assertEquals($definition['description'] ?? null, $option['description']);
|
||||
if (isset($definition['url'])) {
|
||||
$this->assertEquals($definition['url']->out(true), $option['url']);
|
||||
$this->assertTrue($option['hasurl']);
|
||||
}
|
||||
if (isset($definition['icon'])) {
|
||||
$this->assertEquals($definition['iconexport'], $option['icon']);
|
||||
$this->assertTrue($option['hasicon']);
|
||||
}
|
||||
if ($selected) {
|
||||
$this->assertTrue($option['selected']);
|
||||
}
|
||||
if (isset($definition['extras'])) {
|
||||
foreach ($option['extras'] as $extra) {
|
||||
$attribute = $extra['attribute'];
|
||||
$this->assertEquals($definition['extras'][$attribute], $extra['value']);
|
||||
}
|
||||
} else {
|
||||
$this->assertFalse(isset($option['extras']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2624,6 +2624,25 @@ input[disabled] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// Any dialog (modal or dropdown) forced max-widths limits.
|
||||
.dialog-big {
|
||||
max-width: $modal-md;
|
||||
}
|
||||
|
||||
.dialog-small {
|
||||
max-width: $modal-sm;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.dialog-big {
|
||||
width: $modal-md;
|
||||
}
|
||||
|
||||
.dialog-small {
|
||||
width: $modal-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji picker.
|
||||
$picker-width: 350px !default;
|
||||
$picker-width-xs: 320px !default;
|
||||
|
||||
@@ -25432,6 +25432,22 @@ input[disabled] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.dialog-big {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.dialog-small {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.dialog-big {
|
||||
width: 500px;
|
||||
}
|
||||
.dialog-small {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
.emoji-picker {
|
||||
width: 350px;
|
||||
height: 400px;
|
||||
|
||||
@@ -25432,6 +25432,22 @@ input[disabled] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.dialog-big {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.dialog-small {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.dialog-big {
|
||||
width: 500px;
|
||||
}
|
||||
.dialog-small {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
.emoji-picker {
|
||||
width: 350px;
|
||||
height: 400px;
|
||||
|
||||
Reference in New Issue
Block a user