mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 17:06:53 +02:00
MDL-30637 Simplify moodle forms
Make the forms easier to use and navigate by adding functionality to collapse and extend form sections (headers). The logic is as follows: If form contains 2 and less sections (headers): * Display the form as non-collapsible at all. * The point above can be overridden if developer marks the section as expanded in form definition (e.g. $mform->setExpanded('foo')); If form contains 3 and more sections (headers): * always expanding the first section and closing all others by default; * always expanding a section containing at least one "required" element; * expanding any section which contains validation errors after submission; * expanding any section which was previously open on previous submit (e.g. when adding new choices); * expanding the section which is marked as expanded in form definition (e.g. $mform->setExpanded('foo');
This commit is contained in:
parent
1918a2452e
commit
a4067bfc48
3 changed files with 259 additions and 19 deletions
96
lib/form/yui/shortforms/shortforms.js
vendored
Normal file
96
lib/form/yui/shortforms/shortforms.js
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
YUI.add('moodle-form-shortforms', function(Y) {
|
||||||
|
/**
|
||||||
|
* Provides the form shortforms class
|
||||||
|
*
|
||||||
|
* @module moodle-form-shortforms
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for a shortforms
|
||||||
|
*
|
||||||
|
* @param {Object} config Object literal specifying shortforms configuration properties.
|
||||||
|
* @class M.form.shortforms
|
||||||
|
* @constructor
|
||||||
|
* @extends Y.Base
|
||||||
|
*/
|
||||||
|
function SHORTFORMS(config) {
|
||||||
|
SHORTFORMS.superclass.constructor.apply(this, [config]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var SELECTORS = {
|
||||||
|
FIELDSETCOLLAPSIBLE : 'fieldset.collapsible',
|
||||||
|
LEGENDFTOGGLER : 'legend.ftoggler'
|
||||||
|
},
|
||||||
|
CSS = {
|
||||||
|
COLLAPSED : 'collapsed',
|
||||||
|
FHEADER : 'fheader',
|
||||||
|
JSPROCESSED : 'jsprocessed'
|
||||||
|
},
|
||||||
|
ATTRS = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static property provides a string to identify the JavaScript class.
|
||||||
|
*
|
||||||
|
* @property NAME
|
||||||
|
* @type String
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
SHORTFORMS.NAME = 'moodle-form-shortforms';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static property used to define the default attribute configuration for the Shortform.
|
||||||
|
*
|
||||||
|
* @property ATTRS
|
||||||
|
* @type String
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
SHORTFORMS.ATTRS = ATTRS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form ID attribute definition
|
||||||
|
*
|
||||||
|
* @attribute formid
|
||||||
|
* @type String
|
||||||
|
* @default ''
|
||||||
|
* @writeOnce
|
||||||
|
*/
|
||||||
|
ATTRS.formid = {
|
||||||
|
value : null
|
||||||
|
};
|
||||||
|
|
||||||
|
Y.extend(SHORTFORMS, Y.Base, {
|
||||||
|
initializer : function() {
|
||||||
|
var fieldlist = Y.Node.all('#'+this.get('formid')+' '+SELECTORS.FIELDSETCOLLAPSIBLE);
|
||||||
|
// Look through collapsible fieldset divs
|
||||||
|
fieldlist.each(this.process_fieldset, this);
|
||||||
|
// Subscribe collapsible fieldsets to click event
|
||||||
|
Y.one('#'+this.get('formid')).delegate('click', this.switch_state, SELECTORS.FIELDSETCOLLAPSIBLE+' .'+CSS.FHEADER);
|
||||||
|
},
|
||||||
|
process_fieldset : function(fieldset) {
|
||||||
|
fieldset.addClass(CSS.JSPROCESSED);
|
||||||
|
// Get legend element
|
||||||
|
var legendelement = fieldset.one(SELECTORS.LEGENDFTOGGLER);
|
||||||
|
|
||||||
|
// Turn headers to links for accessibility
|
||||||
|
var headerlink = Y.Node.create('<a href="#"></a>');
|
||||||
|
headerlink.addClass(CSS.FHEADER);
|
||||||
|
headerlink.appendChild(legendelement.get('firstChild'));
|
||||||
|
legendelement.prepend(headerlink);
|
||||||
|
},
|
||||||
|
switch_state : function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var fieldset = this.ancestor(SELECTORS.FIELDSETCOLLAPSIBLE);
|
||||||
|
// toggle collapsed class
|
||||||
|
fieldset.toggleClass(CSS.COLLAPSED);
|
||||||
|
// get corresponding hidden variable
|
||||||
|
var statuselement = new Y.one('input[name=mform_isexpanded_'+fieldset.get('id')+']');
|
||||||
|
// and invert it
|
||||||
|
statuselement.set('value', Math.abs(Number(statuselement.get('value'))-1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
M.form = M.form || {};
|
||||||
|
M.form.shortforms = M.form.shortforms || function(params) {
|
||||||
|
return new SHORTFORMS(params);
|
||||||
|
};
|
||||||
|
}, '@VERSION@', {requires:['base', 'node', 'selector-css3']});
|
178
lib/formslib.php
178
lib/formslib.php
|
@ -1069,6 +1069,9 @@ abstract class moodleform {
|
||||||
$mform->setType($elementname, $params);
|
$mform->setType($elementname, $params);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'expanded' :
|
||||||
|
$mform->setExpanded($realelementname, $params);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1260,7 +1263,25 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
|
||||||
/** @var array Array whose keys are element names. If the key exists this is a advanced element */
|
/** @var array Array whose keys are element names. If the key exists this is a advanced element */
|
||||||
var $_advancedElements = array();
|
var $_advancedElements = array();
|
||||||
|
|
||||||
/** @var bool Whether to display advanced elements (on page load) */
|
/**
|
||||||
|
* Array whose keys are element names and the the boolean values reflect the current state. If the key exists this is a collapsible element.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
var $_collapsibleElements = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable shortforms for this form
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
var $_disableShortforms = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to display advanced elements (on page load)
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
var $_showAdvanced = null;
|
var $_showAdvanced = null;
|
||||||
|
|
||||||
/** @var bool whether to automatically initialise M.formchangechecker for this form. */
|
/** @var bool whether to automatically initialise M.formchangechecker for this form. */
|
||||||
|
@ -1280,6 +1301,14 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
|
||||||
*/
|
*/
|
||||||
var $_pageparams = '';
|
var $_pageparams = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of headers the form should contain in order not to be
|
||||||
|
* defined as collapsible.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
var $_non_collapsible_headers = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
|
* Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
|
||||||
*
|
*
|
||||||
|
@ -1348,6 +1377,39 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
|
||||||
$this->setType('mform_showadvanced_last', PARAM_INT);
|
$this->setType('mform_showadvanced_last', PARAM_INT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this method to indicate that the fieldset should be shown as expanded.
|
||||||
|
* The method is applicable to header elements only.
|
||||||
|
*
|
||||||
|
* @param string $headerName header element name
|
||||||
|
* @param boolean $expanded default true sets the element to expanded. False makes the element collapsed.
|
||||||
|
*/
|
||||||
|
function setExpanded($headerName, $expanded=true){
|
||||||
|
if ($this->getElementType('mform_isexpanded_'.$headerName)===false) {
|
||||||
|
// see if we the form has been submitted already
|
||||||
|
$formexpanded = optional_param('mform_isexpanded_'.$headerName, -1, PARAM_INT);
|
||||||
|
if (!$expanded && $formexpanded != -1) {
|
||||||
|
// override expanded state with the form variable
|
||||||
|
$expanded = $formexpanded;
|
||||||
|
}
|
||||||
|
// create the form element for storing expanded state
|
||||||
|
$this->addElement('hidden', 'mform_isexpanded_'.$headerName);
|
||||||
|
$this->setType('mform_isexpanded_'.$headerName, PARAM_INT);
|
||||||
|
$this->setConstant('mform_isexpanded_' . $headerName, (int)$expanded);
|
||||||
|
}
|
||||||
|
$this->_collapsibleElements[$headerName] = !$expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this method to indicate that the form will not be using shortforms.
|
||||||
|
*
|
||||||
|
* @param boolean $disable default true, controls if the shortforms are disabled.
|
||||||
|
*/
|
||||||
|
function setDisableShortforms ($disable = true) {
|
||||||
|
$this->_disableShortforms = $disable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether to show advanced elements in the form on first displaying form. Default is not to
|
* Set whether to show advanced elements in the form on first displaying form. Default is not to
|
||||||
* display advanced elements in the form until 'Show Advanced' is pressed.
|
* display advanced elements in the form until 'Show Advanced' is pressed.
|
||||||
|
@ -1427,9 +1489,9 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
|
||||||
*/
|
*/
|
||||||
function accept(&$renderer) {
|
function accept(&$renderer) {
|
||||||
if (method_exists($renderer, 'setAdvancedElements')){
|
if (method_exists($renderer, 'setAdvancedElements')){
|
||||||
//check for visible fieldsets where all elements are advanced
|
//Check for visible fieldsets where all elements are advanced
|
||||||
//and mark these headers as advanced as well.
|
//and mark these headers as advanced as well.
|
||||||
//And mark all elements in a advanced header as advanced
|
//Also mark all elements in a advanced header as advanced.
|
||||||
$stopFields = $renderer->getStopFieldSetElements();
|
$stopFields = $renderer->getStopFieldSetElements();
|
||||||
$lastHeader = null;
|
$lastHeader = null;
|
||||||
$lastHeaderAdvanced = false;
|
$lastHeaderAdvanced = false;
|
||||||
|
@ -1462,7 +1524,58 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
|
||||||
$this->setAdvanced($lastHeader->getName());
|
$this->setAdvanced($lastHeader->getName());
|
||||||
}
|
}
|
||||||
$renderer->setAdvancedElements($this->_advancedElements);
|
$renderer->setAdvancedElements($this->_advancedElements);
|
||||||
|
}
|
||||||
|
if (method_exists($renderer, 'setCollapsibleElements') && !$this->_disableShortforms){
|
||||||
|
// Check how many headers we have in total, if less than $_non_collapsible_headers,
|
||||||
|
// the form should not be collapsible at all (unless overidden in the form definition).
|
||||||
|
$headercounter = 0;
|
||||||
|
foreach (array_keys($this->_elements) as $elementIndex){
|
||||||
|
$element =& $this->_elements[$elementIndex];
|
||||||
|
if ($element->getType()=='header') {
|
||||||
|
$headercounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($headercounter > $this->_non_collapsible_headers) {
|
||||||
|
// So, we have more than $_non_collapsible_headers headers
|
||||||
|
// add all headers to collapsible elements array (if they have not been added yet).
|
||||||
|
unset($lastHeader);
|
||||||
|
$lastHeader = null;
|
||||||
|
$anyRequiredOrError = false;
|
||||||
|
$headercounter = 0;
|
||||||
|
foreach (array_keys($this->_elements) as $elementIndex){
|
||||||
|
$element =& $this->_elements[$elementIndex];
|
||||||
|
if ($element->getType()=='header') {
|
||||||
|
if (!is_null($lastHeader)) {
|
||||||
|
// Check if we had any required elements or
|
||||||
|
// we are at the top header that should be expanded by default.
|
||||||
|
if ($anyRequiredOrError || $headercounter === 1) {
|
||||||
|
$this->setExpanded($lastHeader->getName());
|
||||||
|
} elseif (!isset($this->_collapsibleElements[$lastHeader->getName()])) {
|
||||||
|
// Define element as collapsed by default.
|
||||||
|
$this->setExpanded($lastHeader->getName(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$headercounter++;
|
||||||
|
$lastHeader =& $element;
|
||||||
|
$anyRequiredOrError = false;
|
||||||
|
} elseif (in_array($element->getName(), $this->_required) || isset($this->_errors[$element->getName()])) {
|
||||||
|
$anyRequiredOrError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process very last header.
|
||||||
|
if (!is_null($lastHeader)){
|
||||||
|
// Check if we had any required elements or
|
||||||
|
// we are at the top header that should be expanded by default.
|
||||||
|
if ($anyRequiredOrError || $headercounter === 1) {
|
||||||
|
$this->setExpanded($lastHeader->getName());
|
||||||
|
} elseif (!isset($this->_collapsibleElements[$lastHeader->getName()])) {
|
||||||
|
// Define element as collapsed by default.
|
||||||
|
$this->setExpanded($lastHeader->getName(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pass the array to renderer object.
|
||||||
|
$renderer->setCollapsibleElements($this->_collapsibleElements, $this->getAttribute('id'));
|
||||||
}
|
}
|
||||||
parent::accept($renderer);
|
parent::accept($renderer);
|
||||||
}
|
}
|
||||||
|
@ -2228,7 +2341,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
"\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"advancedbutton\">{advancedimg}{button}</div><div class=\"fcontainer clearfix\">\n\t\t";
|
"\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"advancedbutton\">{advancedimg}{button}</div><div class=\"fcontainer clearfix\">\n\t\t";
|
||||||
|
|
||||||
/** @var string Template used when opening a fieldset */
|
/** @var string Template used when opening a fieldset */
|
||||||
var $_openFieldsetTemplate = "\n\t<fieldset class=\"clearfix\" {id}>";
|
var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id}>";
|
||||||
|
|
||||||
/** @var string Template used when closing a fieldset */
|
/** @var string Template used when closing a fieldset */
|
||||||
var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
|
var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
|
||||||
|
@ -2236,11 +2349,25 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
/** @var string Required Note template string */
|
/** @var string Required Note template string */
|
||||||
var $_requiredNoteTemplate =
|
var $_requiredNoteTemplate =
|
||||||
"\n\t\t<div class=\"fdescription required\">{requiredNote}</div>";
|
"\n\t\t<div class=\"fdescription required\">{requiredNote}</div>";
|
||||||
|
/**
|
||||||
/** @var array list of elements which are marked as advance and will be grouped together */
|
* Array whose keys are element names. If the key exists this is a advanced element
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
var $_advancedElements = array();
|
var $_advancedElements = array();
|
||||||
|
|
||||||
/** @var int Whether to display advanced elements (on page load) 1 => show, 0 => hide */
|
/**
|
||||||
|
* Array whose keys are element names and the the boolean values reflect the current state. If the key exists this is a collapsible element.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
var $_collapsibleElements = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to display advanced elements (on page load)
|
||||||
|
*
|
||||||
|
* @var integer 1 means show 0 means hide
|
||||||
|
*/
|
||||||
var $_showAdvanced;
|
var $_showAdvanced;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2274,6 +2401,14 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
$this->_advancedElements = $elements;
|
$this->_advancedElements = $elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting collapsible elements
|
||||||
|
*
|
||||||
|
* @param array $elements
|
||||||
|
*/
|
||||||
|
function setCollapsibleElements($elements) {
|
||||||
|
$this->_collapsibleElements = $elements;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* What to do when starting the form
|
* What to do when starting the form
|
||||||
*
|
*
|
||||||
|
@ -2285,6 +2420,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
$this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
|
$this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
|
||||||
$this->_advancedHTML = $form->getAdvancedHTML();
|
$this->_advancedHTML = $form->getAdvancedHTML();
|
||||||
$this->_showAdvanced = $form->getShowAdvanced();
|
$this->_showAdvanced = $form->getShowAdvanced();
|
||||||
|
$formid = $form->getAttribute('id');
|
||||||
parent::startForm($form);
|
parent::startForm($form);
|
||||||
if ($form->isFrozen()){
|
if ($form->isFrozen()){
|
||||||
$this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
|
$this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
|
||||||
|
@ -2297,11 +2433,14 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
$PAGE->requires->yui_module('moodle-core-formchangechecker',
|
$PAGE->requires->yui_module('moodle-core-formchangechecker',
|
||||||
'M.core_formchangechecker.init',
|
'M.core_formchangechecker.init',
|
||||||
array(array(
|
array(array(
|
||||||
'formid' => $form->getAttribute('id')
|
'formid' => $formid
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
$PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
|
$PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
|
||||||
}
|
}
|
||||||
|
if (count($this->_collapsibleElements)) {
|
||||||
|
$PAGE->requires->yui_module('moodle-form-shortforms', 'M.form.shortforms', array(array('formid' => $formid)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2472,17 +2611,18 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
|
||||||
$this->_fieldsetsOpen--;
|
$this->_fieldsetsOpen--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define collapsible classes for fieldsets
|
||||||
|
$fieldsetclasses = array('clearfix');
|
||||||
|
if (isset($this->_collapsibleElements[$name])) {
|
||||||
|
$fieldsetclasses[] = 'collapsible';
|
||||||
|
if ($this->_collapsibleElements[$name]) {
|
||||||
|
$fieldsetclasses[] = 'collapsed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
|
$openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
|
||||||
if ($this->_showAdvanced){
|
$openFieldsetTemplate = str_replace('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate);
|
||||||
$advclass = ' class="advanced"';
|
|
||||||
} else {
|
|
||||||
$advclass = ' class="advanced hide"';
|
|
||||||
}
|
|
||||||
if (isset($this->_advancedElements[$name])){
|
|
||||||
$openFieldsetTemplate = str_replace('{advancedclass}', $advclass, $openFieldsetTemplate);
|
|
||||||
} else {
|
|
||||||
$openFieldsetTemplate = str_replace('{advancedclass}', '', $openFieldsetTemplate);
|
|
||||||
}
|
|
||||||
$this->_html .= $openFieldsetTemplate . $header_html;
|
$this->_html .= $openFieldsetTemplate . $header_html;
|
||||||
$this->_fieldsetsOpen++;
|
$this->_fieldsetsOpen++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,6 +234,10 @@ a.skip:active {position: static;display: block;}
|
||||||
.mform fieldset.hidden {border-width:0;}
|
.mform fieldset.hidden {border-width:0;}
|
||||||
.mform fieldset.group {margin-bottom: 0}
|
.mform fieldset.group {margin-bottom: 0}
|
||||||
.mform fieldset.error {border: 1px solid #A00;}
|
.mform fieldset.error {border: 1px solid #A00;}
|
||||||
|
.mform fieldset.collapsible legend a.fheader {padding: 0 5px 0 15px; background: url([[pix:t/expanded]]) 2px center no-repeat;}
|
||||||
|
.mform fieldset.collapsed legend a.fheader {background: url([[pix:t/collapsed]]) 2px center no-repeat;}
|
||||||
|
.mform fieldset.collapsed.jsprocessed {border-width: 1px 0 0 0; padding: 0;}
|
||||||
|
.mform fieldset.collapsed.jsprocessed div.fcontainer {display: none;}
|
||||||
.mform .fitem {width:100%;overflow:hidden;margin-top:5px;margin-bottom:1px;clear:right;}
|
.mform .fitem {width:100%;overflow:hidden;margin-top:5px;margin-bottom:1px;clear:right;}
|
||||||
.mform .fitem .fitemtitle {width:15%;text-align:right;float:left;}
|
.mform .fitem .fitemtitle {width:15%;text-align:right;float:left;}
|
||||||
.mform .fitem .fitemtitle div {display: inline;}
|
.mform .fitem .fitemtitle div {display: inline;}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue