Permalink
Browse files

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');
  • Loading branch information...
1 parent 1918a24 commit a4067bfc48d995e9320844c109ebfb5b10ca001e @kabalin kabalin committed Feb 7, 2013
Showing with 259 additions and 19 deletions.
  1. +96 −0 lib/form/yui/shortforms/shortforms.js
  2. +159 −19 lib/formslib.php
  3. +4 −0 theme/base/style/core.css
View
96 lib/form/yui/shortforms/shortforms.js
@@ -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']});
View
178 lib/formslib.php
@@ -1069,6 +1069,9 @@ function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname,
$mform->setType($elementname, $params);
}
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 $_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 bool whether to automatically initialise M.formchangechecker for this form. */
@@ -1281,6 +1302,14 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
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
*
* @staticvar int $formcounter counts number of forms
@@ -1348,6 +1377,39 @@ function setAdvanced($elementName, $advanced=true){
$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
* display advanced elements in the form until 'Show Advanced' is pressed.
@@ -1427,9 +1489,9 @@ public function is_form_change_checker_enabled() {
*/
function accept(&$renderer) {
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 all elements in a advanced header as advanced
+ //Also mark all elements in a advanced header as advanced.
$stopFields = $renderer->getStopFieldSetElements();
$lastHeader = null;
$lastHeaderAdvanced = false;
@@ -1462,7 +1524,58 @@ function accept(&$renderer) {
$this->setAdvanced($lastHeader->getName());
}
$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);
}
@@ -2228,19 +2341,33 @@ 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";
/** @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 $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
/** @var string Required Note template string */
var $_requiredNoteTemplate =
"\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 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;
/**
@@ -2275,6 +2402,14 @@ function setAdvancedElements($elements){
}
/**
+ * Setting collapsible elements
+ *
+ * @param array $elements
+ */
+ function setCollapsibleElements($elements) {
+ $this->_collapsibleElements = $elements;
+ }
+ /**
* What to do when starting the form
*
* @param MoodleQuickForm $form reference of the form
@@ -2285,6 +2420,7 @@ function startForm(&$form){
$this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates);
$this->_advancedHTML = $form->getAdvancedHTML();
$this->_showAdvanced = $form->getShowAdvanced();
+ $formid = $form->getAttribute('id');
parent::startForm($form);
if ($form->isFrozen()){
$this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
@@ -2297,11 +2433,14 @@ function startForm(&$form){
$PAGE->requires->yui_module('moodle-core-formchangechecker',
'M.core_formchangechecker.init',
array(array(
- 'formid' => $form->getAttribute('id')
+ 'formid' => $formid
))
);
$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 @@ function renderHeader(&$header) {
$this->_fieldsetsOpen--;
}
- $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
- if ($this->_showAdvanced){
- $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);
+ // 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('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate);
+
$this->_html .= $openFieldsetTemplate . $header_html;
$this->_fieldsetsOpen++;
}
View
4 theme/base/style/core.css
@@ -234,6 +234,10 @@ a.skip:active {position: static;display: block;}
.mform fieldset.hidden {border-width:0;}
.mform fieldset.group {margin-bottom: 0}
.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 .fitemtitle {width:15%;text-align:right;float:left;}
.mform .fitem .fitemtitle div {display: inline;}

0 comments on commit a4067bf

Please sign in to comment.