mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 20:42:22 +02:00
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:
parent
02de5b79fd
commit
3344354641
@ -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;
|
||||
}
|
||||
|
||||
|
19
lib/form/amd/build/choicedropdown.min.js
vendored
Normal file
19
lib/form/amd/build/choicedropdown.min.js
vendored
Normal 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
|
1
lib/form/amd/build/choicedropdown.min.js.map
Normal file
1
lib/form/amd/build/choicedropdown.min.js.map
Normal file
File diff suppressed because one or more lines are too long
158
lib/form/amd/src/choicedropdown.js
Normal file
158
lib/form/amd/src/choicedropdown.js
Normal 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
210
lib/form/choicedropdown.php
Normal 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;
|
||||
}
|
||||
}
|
149
lib/form/templates/element-choicedropdown.mustache
Normal file
149
lib/form/templates/element-choicedropdown.mustache
Normal 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}}
|
42
lib/form/tests/behat/choicedropdown.feature
Normal file
42
lib/form/tests/behat/choicedropdown.feature
Normal 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
|
117
lib/form/tests/behat/fixtures/field_choicedropdown_testpage.php
Normal file
117
lib/form/tests/behat/fixtures/field_choicedropdown_testpage.php
Normal 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();
|
@ -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');
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user