MDL-79985 forms: add choicedropdown form element

The new quick form field uses a choice list to display a dropdown
component inside a form. Apart from que quick form code, the patch also
include a small improvement to the choicelist class to disable empty
values. This small patch is needed because the quickform field won't
allow empty values.
This commit is contained in:
Ferran Recio 2023-11-13 14:37:30 +01:00
parent 02de5b79fd
commit 3344354641
10 changed files with 809 additions and 5 deletions

View File

@ -41,6 +41,9 @@ class choicelist implements renderable, named_templatable {
/** @var string the choice description. */
protected $description = null;
/** @var bool if the selected value can be empty. */
protected $allowempty = null;
/**
* Constructor.
*
@ -106,9 +109,37 @@ class choicelist implements renderable, named_templatable {
* @return string|null The value of the selected option.
*/
public function get_selected_value(): ?string {
if (empty($this->selected) && !$this->allowempty && !empty($this->options)) {
return array_key_first($this->options);
}
return $this->selected;
}
/**
* Set the allow empty option.
* @param bool $allowempty Whether the selected value can be empty.
*/
public function set_allow_empty(bool $allowempty) {
$this->allowempty = $allowempty;
}
/**
* Get the allow empty option.
* @return bool Whether the selected value can be empty.
*/
public function get_allow_empty(): bool {
return $this->allowempty;
}
/**
* Check if the value is in the options.
* @param string $value The value to check.
* @return bool
*/
public function has_value(string $value): bool {
return isset($this->options[$value]);
}
/**
* Set the general choice description option.
*
@ -140,7 +171,9 @@ class choicelist implements renderable, named_templatable {
}
/**
* Set the option disabled.
* Sets the HTML attributes to the option.
*
* This method will remove any previous extra attributes.
*
* @param string $value The value of the option.
* @param array $extras an array to add HTML attributes to the option (attribute => value).
@ -149,14 +182,48 @@ class choicelist implements renderable, named_templatable {
if (!isset($this->options[$value])) {
return;
}
$extrasattributes = [];
$this->options[$value]['extras'] = [];
$this->add_option_extras($value, $extras);
}
/**
* Add HTML attributes to the option.
* @param string $value The value of the option.
* @param array $extras an array to add HTML attributes to the option (attribute => value).
*/
public function add_option_extras(string $value, array $extras) {
if (!isset($this->options[$value])) {
return;
}
if (!isset($this->options[$value]['extras'])) {
$this->options[$value]['extras'] = [];
}
foreach ($extras as $attribute => $attributevalue) {
$extrasattributes[] = [
$this->options[$value]['extras'][] = [
'attribute' => $attribute,
'value' => $attributevalue,
];
}
$this->options[$value]['extras'] = $extrasattributes;
}
/**
* Get the selected option HTML.
*
* This method is used to display the selected option and the option icon.
*
* @param renderer_base $output The renderer.
* @return string
*/
public function get_selected_content(renderer_base $output): string {
if (empty($this->selected)) {
return '';
}
$option = $this->options[$this->selected];
$icon = '';
if (!empty($option['icon'])) {
$icon = $output->render($option['icon']);
}
return $icon . $option['name'];
}
/**
@ -178,7 +245,7 @@ class choicelist implements renderable, named_templatable {
}
$option['hasurl'] = !empty($option['url']);
if ($option['value'] == $this->selected) {
if ($option['value'] == $this->get_selected_value()) {
$option['selected'] = true;
}

View File

@ -0,0 +1,19 @@
define("core_form/choicedropdown",["exports","core/local/dropdown/status","core_form/changechecker"],(function(_exports,_status,_changechecker){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* Field controller for choicedropdown field.
*
* @module core_form/choicedropdown
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Classes_hidden="d-none";
/**
* Internal form element class.
*
* @private
* @class FieldController
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/class FieldController{constructor(elementId){this.elementId=elementId,this.mainSelect=document.getElementById(this.elementId),this.dropdown=(0,_status.getDropdownStatus)('[data-form-controls="'.concat(this.elementId,'"]')),this.dropdown.getElement().classList.remove(Classes_hidden)}addEventListeners(){this.dropdown.getElement().addEventListener("change",this.updateSelect.bind(this)),this.dropdown.getElement().addEventListener("click",(event=>event.preventDefault())),this.mainSelect.addEventListener("change",this.updateDropdown.bind(this));new MutationObserver((mutations=>{mutations.forEach((mutation=>{"attributes"===mutation.type&&"disabled"===mutation.attributeName&&this.updateDropdown()}))})).observe(this.mainSelect,{attributeFilter:["disabled"]})}isDisabled(){var _this$mainSelect;return null===(_this$mainSelect=this.mainSelect)||void 0===_this$mainSelect?void 0:_this$mainSelect.hasAttribute("disabled")}async updateDropdown(){this.dropdown.setButtonDisabled(this.isDisabled()),this.dropdown.getSelectedValue()!=this.mainSelect.value&&this.dropdown.setSelectedValue(this.mainSelect.value)}async updateSelect(){this.dropdown.getSelectedValue()!=this.mainSelect.value&&(this.mainSelect.value=this.dropdown.getSelectedValue(),(0,_changechecker.markFormAsDirty)(this.mainSelect.closest("form")),this.mainSelect.dispatchEvent(new Event("change")))}disableInteractiveDialog(){var _this$mainSelect2;null===(_this$mainSelect2=this.mainSelect)||void 0===_this$mainSelect2||_this$mainSelect2.classList.remove(Classes_hidden);this.dropdown.getElement().classList.add(Classes_hidden)}hasForceDialog(){var _this$mainSelect3;return!(null===(_this$mainSelect3=this.mainSelect)||void 0===_this$mainSelect3||!_this$mainSelect3.dataset.forceDialog)}}_exports.init=elementId=>{const field=new FieldController(elementId);!document.body.classList.contains("behat-site")||field.hasForceDialog()?field.addEventListeners():field.disableInteractiveDialog()}}));
//# sourceMappingURL=choicedropdown.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,158 @@
// 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/>.
/**
* Field controller for choicedropdown field.
*
* @module core_form/choicedropdown
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {getDropdownStatus} from 'core/local/dropdown/status';
import {markFormAsDirty} from 'core_form/changechecker';
const Classes = {
notClickable: 'not-clickable',
hidden: 'd-none',
};
/**
* Internal form element class.
*
* @private
* @class FieldController
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class FieldController {
/**
* Class constructor.
*
* @param {String} elementId Form element id
*/
constructor(elementId) {
this.elementId = elementId;
this.mainSelect = document.getElementById(this.elementId);
this.dropdown = getDropdownStatus(`[data-form-controls="${this.elementId}"]`);
this.dropdown.getElement().classList.remove(Classes.hidden);
}
/**
* Add form element event listener.
*/
addEventListeners() {
this.dropdown.getElement().addEventListener(
'change',
this.updateSelect.bind(this)
);
// Click on a dropdown link can trigger a wrong dirty form reload warning.
this.dropdown.getElement().addEventListener(
'click',
(event) => event.preventDefault()
);
this.mainSelect.addEventListener(
'change',
this.updateDropdown.bind(this)
);
// Enabling or disabling the select does not trigger any JS event.
const observerCallback = (mutations) => {
mutations.forEach((mutation) => {
if (mutation.type !== 'attributes' || mutation.attributeName !== 'disabled') {
return;
}
this.updateDropdown();
});
};
new MutationObserver(observerCallback).observe(
this.mainSelect,
{attributeFilter: ['disabled']}
);
}
/**
* Check if the field is disabled.
* @returns {Boolean}
*/
isDisabled() {
return this.mainSelect?.hasAttribute('disabled');
}
/**
* Update selected option preview in form.
*/
async updateDropdown() {
this.dropdown.setButtonDisabled(this.isDisabled());
if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
return;
}
this.dropdown.setSelectedValue(this.mainSelect.value);
}
/**
* Update selected option preview in form.
*/
async updateSelect() {
if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
return;
}
this.mainSelect.value = this.dropdown.getSelectedValue();
markFormAsDirty(this.mainSelect.closest('form'));
// Change the select element via JS does not trigger the standard change event.
this.mainSelect.dispatchEvent(new Event('change'));
}
/**
* Disable the choice dialog and convert it into a regular select field.
*/
disableInteractiveDialog() {
this.mainSelect?.classList.remove(Classes.hidden);
const dropdownElement = this.dropdown.getElement();
dropdownElement.classList.add(Classes.hidden);
}
/**
* Check if the field has a force dialog attribute.
// *
* The force dialog is a setting to force the javascript control even in
* behat test.
*
* @returns {Boolean} if the dialog modal should be forced or not
*/
hasForceDialog() {
return !!this.mainSelect?.dataset.forceDialog;
}
}
/**
* Initialises a choice dialog field.
*
* @method init
* @param {String} elementId Form element id
* @listens event:uploadStarted
* @listens event:uploadCompleted
*/
export const init = (elementId) => {
const field = new FieldController(elementId);
// This field is just a select wrapper. To optimize tests, we don't want to keep behat
// waiting for extra loadings in this case. The set field steps are about testing other
// stuff, not to test fancy javascript form fields. However, we keep the possibility of
// testing the javascript part using behat when necessary.
if (document.body.classList.contains('behat-site') && !field.hasForceDialog()) {
field.disableInteractiveDialog();
return;
}
field.addEventListeners();
};

210
lib/form/choicedropdown.php Normal file
View File

@ -0,0 +1,210 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
use core\output\choicelist;
use core\output\local\dropdown\status;
require_once('HTML/QuickForm/select.php');
require_once('templatable_form_element.php');
/**
* User choice using a dropdown type form element.
*
* @package core_form
* @category form
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_choicedropdown extends HTML_QuickForm_select implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/**
* @var string html for help button, if empty then no help.
*/
protected string $_helpbutton = '';
/**
* @var bool if true label will be hidden.
*/
protected bool $_hiddenLabel = false;
/**
* @var choicelist the user choices.
*/
protected ?choicelist $choice = null;
/**
* @var string[] Dropdown dialog width.
*/
public const WIDTH = status::WIDTH;
/**
* @var string the dropdown width (from core\output\local\dropdown\status::WIDTH).
*/
protected string $dropdownwidth = status::WIDTH['small'];
/**
* Constructor.
*
* @param string $elementname Select name attribute
* @param mixed $elementlabel Label(s) for the select
* @param choicelist $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct(
$elementname = null,
$elementlabel = null,
choicelist $options = null,
$attributes = null
) {
parent::__construct($elementname, $elementlabel, $options, $attributes);
$this->_type = 'choicedropdown';
}
/**
* Set the dropdown width.
*
* @param string $width
*/
public function set_dialog_width(string $width) {
$this->dropdownwidth = $width;
}
/**
* Loads options from a choicelist.
*
* @param choicelist $choice Options source currently supports assoc array or DB_result
* @param string|null $value optional value (in case it is not defined in the choicelist)
* @param string|null $unused2 unused
* @param string|null $unused3 unused
* @param string|null $unused4 unused
* @return bool
*/
public function load(&$choice, $value = null, $unused2 = null, $unused3 = null, $unused4 = null): bool {
if (!$choice instanceof choicelist) {
throw new coding_exception('Choice must be instance of choicelist');
}
$this->choice = $choice;
$this->choice->set_allow_empty(false);
if ($value !== null && is_string($value)) {
$choice->set_selected_value($value);
}
$this->setSelected($choice->get_selected_value());
return true;
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
/**
* Returns HTML for select form element.
*
* This method is only needed when forms renderer is forces via
* $GLOBALS['_HTML_QuickForm_default_renderer']. Otherwise the
* renderer will use mustache templates.
*
* @return string
*/
public function toHtml(): string {
$html = '';
if ($this->_hiddenLabel) {
$this->_generateId();
$html .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.$this->getLabel().'</label>';
}
$html .= parent::toHtml();
return $html;
}
/**
* get html for help button
*
* @return string html for help button
*/
public function getHelpButton(): string {
return $this->_helpbutton;
}
/**
* Slightly different container template when frozen. Don't want to use a label tag
* with a for attribute in that case for the element label but instead use a div.
* Templates are defined in renderer constructor.
*
* @return string
*/
public function getElementTemplateType(): string {
if ($this->_flagFrozen) {
return 'static';
} else {
return 'default';
}
}
/**
* We check the options and return only the values that _could_ have been
* selected. We also return a scalar value if select is not "multiple"
*
* @param string $submitvalues submitted values
* @param bool $assoc if true the returned value is associated array
* @return string|null
*/
public function exportValue(&$submitvalues, $assoc = false) {
$value = $this->_findValue($submitvalues) ?? $this->getValue();
if (is_array($value)) {
$value = reset($value);
}
if ($value === null) {
return $this->_prepareValue($value, $assoc);
}
if (!$this->choice->has_value($value)) {
$value = $this->choice->get_selected_value();
}
return $this->_prepareValue($value, $assoc);
}
public function export_for_template(renderer_base $output): array {
$context = $this->export_for_template_base($output);
if (!empty($this->_values)) {
$this->choice->set_selected_value(reset($this->_values));
}
$dialog = new status(
$this->choice->get_selected_content($output),
$this->choice,
[
'extras' => ['data-form-controls' => $context['id']],
'buttonsync' => true,
'updatestatus' => true,
'dialogwidth' => $this->dropdownwidth,
]
);
$context['dropdown'] = $dialog->export_for_template($output);
$context['select'] = $this->choice->export_for_template($output);
$context['nameraw'] = $this->getName();
return $context;
}
}

View File

@ -0,0 +1,149 @@
{{!
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_form/element-choicedropdown
Choice dialog form element template.
Context variables required for this template:
* id - Element id,
* nameraw - Raw Element name without '[]'
* name - Element name,
* label - Element label,
* multiple - multi select?,
* checked - checked?,
* error - Is there an error associated with this element?,
* size - Element size,
* value - Element value,
* helpbutton - Helpbutton,
* hiddenlabel - Element hidden flag,
* frozen - Element frozen flag,
* hardfrozen - Element hard fronzen flag,
* extraclasses - Extra classes assocaited,
* type - Element type,
* attributes - Element attributes,
* options - [
{
text - Option text,
value - Option value,
selected - Selected?,
disabled - Disabled?,
optionattributes - Option attributes
}
]
Example context (json):
{
"element": {
"wrapperid": "fitem_id_choicedropdownfield",
"iderror": "error_id_choicedropdownfield",
"id": "id_choicedropdownfield",
"nameraw": "choicedropdownfield",
"name": "choicedropdownfield",
"label": null,
"multiple": null,
"checked": null,
"error": null,
"size": null,
"value": null,
"helpbutton": "",
"hiddenlabel": false,
"frozen": false,
"hardfrozen": false,
"extraclasses": null,
"type": "select",
"attributes": "",
"fieldicon": "<a class='btn'><i class='icon fa fa-ellipsis-h'></i></a>",
"options": [
{
"rawoptionid": "1",
"text": "Hidden",
"value": 0,
"selected": false,
"disabled": false,
"optionattributes": ""
},
{
"rawoptionid": "2",
"text": "Visible to everyone",
"value": 1,
"selected": true,
"disabled": false,
"optionattributes": ""
},
{
"rawoptionid": "3",
"text": "Allow only other course members to see my email address",
"value": 2,
"selected": false,
"disabled": false,
"optionattributes": ""
}
]
}
}
}}
{{< core_form/element-template }}
{{$element}}
{{^element.frozen}}
<select
class="custom-select d-none {{#error}}is-invalid{{/error}}"
name="{{element.name}}"
id="{{element.id}}"
data-region="choice-select"
{{#element.multiple}}multiple{{/element.multiple}}
{{#element.size}}size="{{element.size}}"{{/element.size}}
{{#error}} autofocus aria-describedby="{{element.iderror}}" {{/error}}
{{{element.attributes}}}
>
{{#element.select}}
{{#options}}
<option
value="{{value}}"
data-optionid="{{element.id}}_{{optionuniqid}}"
{{#selected}}selected{{/selected}}
{{#disabled}}disabled{{/disabled}}
{{{optionattributes}}}
>
{{{name}}}
</option>
{{/options}}
{{/element.select}}
</select>
{{#element.dropdown}}
{{< core/local/dropdown/status}}
{{$ buttonclasses }} btn btn-outline-secondary dropdown-toggle {{/ buttonclasses }}
{{/ core/local/dropdown/status}}
{{/element.dropdown}}
{{/element.frozen}}
{{#element.frozen}}
{{#element.options}}
{{#selected}}
{{{text}}}
{{^element.hardfrozen}}
<input
type="hidden"
name="{{element.name}}"
value="{{value}}"
id="{{element.id}}"
>
{{/element.hardfrozen}}
{{/selected}}
{{/element.options}}
{{/element.frozen}}
{{/element}}
{{/ core_form/element-template }}
{{#js}}
require(['core_form/choicedropdown'], function(ChioceDropdown) {
ChioceDropdown.init('{{element.id}}');
});
{{/js}}

View File

@ -0,0 +1,42 @@
@core
Feature: Choice dropdown form behat test
In order to use choicelist in quickforms
As an admin
I need to be able to test it via behat
Background:
Given I log in as "admin"
And I am on fixture page "/lib/form/tests/behat/fixtures/field_choicedropdown_testpage.php"
Scenario: Set some value into choice dropdown
When I set the field "Basic choice dropdown" to "Text option 2"
And I click on "Send form" "button"
Then I should see "example0: option2" in the "submitted_data" "region"
@javascript
Scenario: Set some value into choice dropdown with javascript enabled
When I set the field "Basic choice dropdown" to "Text option 2"
And I click on "Send form" "button"
Then I should see "example0: option2" in the "submitted_data" "region"
@javascript
Scenario: Disable choice dropdown via javascript
When I click on "Check to disable the first choice dropdown field." "checkbox"
Then the "Disable if example" "field" should be disabled
@javascript
Scenario: Hide choice dropdown via javascript
Given I should see "Hide if example"
When I click on "Check to hide the first choice dropdown field." "checkbox"
Then I should not see "Hide if example"
@javascript
Scenario: Use a choice dropdown to disable and hide other fields
Given I should not see "Hide if element"
And the "Disabled if element" "field" should be disabled
When I set the field "Control choice dropdown" to "Show or enable subelements"
Then I should see "Hide if element"
And the "Disabled if element" "field" should be enabled
And I set the field "Control choice dropdown" to "Hide or disable subelements"
And I should not see "Hide if element"
And the "Disabled if element" "field" should be disabled

View File

@ -0,0 +1,117 @@
<?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 choice dropdown field type.
*
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @package core_form
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\output\choicelist;
require_once(__DIR__ . '/../../../../../config.php');
defined('BEHAT_SITE_RUNNING') || die();
global $CFG, $PAGE, $OUTPUT;
require_once($CFG->libdir . '/formslib.php');
$PAGE->set_url('/lib/form/tests/behat/fixtures/field_choicedropdown_testpage.php');
$PAGE->add_body_class('limitedwidth');
require_login();
$PAGE->set_context(core\context\system::instance());
/**
* Class test_choice_dropdown
* @package core_form
*/
class test_choice_dropdown extends moodleform {
/**
* Define the export form.
*/
public function definition() {
$mform = $this->_form;
$options = new choicelist();
$options->set_allow_empty(false);
$options->add_option('option1', "Text option 1", [
'description' => 'Option 1 description',
'icon' => new pix_icon('t/hide', 'Eye icon 1'),
]);
$options->add_option('option2', "Text option 2", [
'description' => 'Option 2 description',
'icon' => new pix_icon('t/stealth', 'Eye icon 2'),
]);
$options->add_option('option3', "Text option 3", [
'description' => 'Option 3 description',
'icon' => new pix_icon('t/show', 'Eye icon 3'),
]);
$mform->addElement('header', 'database', "Basic example");
$mform->addElement('choicedropdown', 'example0', "Basic choice dropdown", $options);
$mform->addElement('header', 'database', "Disable choice dropdown");
$mform->addElement('checkbox', 'disableme', 'Check to disable the first choice dropdown field.');
$mform->addElement('choicedropdown', 'example1', "Disable if example", $options);
$mform->disabledIf('example1', 'disableme', 'checked');
$mform->addElement('header', 'database', "Hide choice dropdown");
$mform->addElement('checkbox', 'hideme', 'Check to hide the first choice dropdown field.');
$mform->addElement('choicedropdown', 'example2', "Hide if example", $options);
$mform->hideIf('example2', 'hideme', 'checked');
$options = new choicelist();
$options->set_allow_empty(false);
$options->add_option('hide', 'Hide or disable subelements');
$options->add_option('show', 'Show or enable subelements');
$mform->addElement('header', 'database', "Use choice dropdown to hide or disable other fields");
$mform->addElement('choicedropdown', 'example3', "Control choice dropdown", $options);
$mform->addElement('text', 'hideinput', 'Hide if element', ['maxlength' => 80, 'size' => 50]);
$mform->hideIf('hideinput', 'example3', 'eq', 'hide');
$mform->setDefault('hideinput', 'Is this visible?');
$mform->setType('hideinput', PARAM_TEXT);
$mform->addElement('text', 'disabledinput', 'Disabled if element', ['maxlength' => 80, 'size' => 50]);
$mform->disabledIf('disabledinput', 'example3', 'eq', 'hide');
$mform->setDefault('disabledinput', 'Is this enabled?');
$mform->setType('disabledinput', PARAM_TEXT);
$this->add_action_buttons(false, 'Send form');
}
}
echo $OUTPUT->header();
echo "<h2>Quickform integration test</h2>";
$form = new test_choice_dropdown();
$data = $form->get_data();
if ($data) {
echo "<h3>Submitted data</h3>";
echo '<div id="submitted_data"><ul>';
$data = (array) $data;
foreach ($data as $field => $value) {
echo "<li id=\"sumbmitted_{$field}\">$field: $value</li>";
}
echo '</ul></div>';
}
$form->display();
echo $OUTPUT->footer();

View File

@ -3683,6 +3683,7 @@ MoodleQuickForm::registerElementType('course', "$CFG->libdir/form/course.php", '
MoodleQuickForm::registerElementType('cohort', "$CFG->libdir/form/cohort.php", 'MoodleQuickForm_cohort');
MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector');
MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
MoodleQuickForm::registerElementType('choicedropdown', "$CFG->libdir/form/choicedropdown.php", 'MoodleQuickForm_choicedropdown');
MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');
MoodleQuickForm::registerElementType('date_time_selector', "$CFG->libdir/form/datetimeselector.php", 'MoodleQuickForm_date_time_selector');
MoodleQuickForm::registerElementType('duration', "$CFG->libdir/form/duration.php", 'MoodleQuickForm_duration');

View File

@ -192,4 +192,44 @@ class choicelist_test extends advanced_testcase {
$this->assertFalse(isset($option['extras']));
}
}
/**
* Test for a choice with option selected.
*
* @covers ::_construct
* @covers ::add_option
* @covers ::set_selected_value
* @covers ::get_selected_value
* @covers ::set_allow_empty
* @covers ::get_allow_empty
* @covers ::export_for_template
*/
public function test_set_allow_empty(): void {
$choice = new choicelist('Choose an option');
$choice->add_option('option1', 'Option 1');
$choice->add_option('option2', 'Option 2');
$choice->set_allow_empty(true);
$this->assertTrue($choice->get_allow_empty());
$this->assertNull($choice->get_selected_value());
$choice->set_allow_empty(false);
$this->assertFalse($choice->get_allow_empty());
$this->assertEquals('option1', $choice->get_selected_value());
// Validate the null selected value is not changed when allow empty is set to true.
$choice->set_allow_empty(true);
$this->assertTrue($choice->get_allow_empty());
$this->assertNull($choice->get_selected_value());
$choice->set_selected_value('option2');
$choice->set_allow_empty(false);
$this->assertFalse($choice->get_allow_empty());
$this->assertEquals('option2', $choice->get_selected_value());
$choice->set_allow_empty(true);
$this->assertTrue($choice->get_allow_empty());
$this->assertEquals('option2', $choice->get_selected_value());
}
}