diff --git a/lib/form/form.js b/lib/form/form.js index 38e518bef44..4fc331fdf22 100644 --- a/lib/form/form.js +++ b/lib/form/form.js @@ -69,8 +69,10 @@ if (typeof M.form.dependencyManager === 'undefined') { names[i] = new Y.NodeList(); for (var condition in conditions) { for (var value in conditions[condition]) { - for (var ei in conditions[condition][value]) { - names[conditions[condition][value][ei]] = new Y.NodeList(); + for (var hide in conditions[condition][value]) { + for (var ei in conditions[condition][value][hide]) { + names[conditions[condition][value][hide][ei]] = new Y.NodeList(); + } } } } @@ -83,6 +85,13 @@ if (typeof M.form.dependencyManager === 'undefined') { names[name].push(node); } }); + // Locate any groups with the given name. + this.get('form').all('.fitem').each(function(node) { + var name = node.getData('groupname'); + if (name && ({}).hasOwnProperty.call(names, name)) { + names[name].push(node); + } + }); this._nameCollections = names; }, @@ -118,7 +127,7 @@ if (typeof M.form.dependencyManager === 'undefined') { var dependencies = this.get('dependencies'), tohide = {}, tolock = {}, - condition, value, lock, hide, + condition, value, isHide, lock, hide, checkfunction, result, elements; if (!({}).hasOwnProperty.call(dependencies, dependon)) { return true; @@ -126,26 +135,28 @@ if (typeof M.form.dependencyManager === 'undefined') { elements = this.elementsByName(dependon); for (condition in dependencies[dependon]) { for (value in dependencies[dependon][condition]) { - checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1); - if (Y.Lang.isFunction(this[checkfunction])) { - result = this[checkfunction].apply(this, [elements, value, e]); - } else { - result = this._dependencyDefault(elements, value, e); - } - lock = result.lock || false; - hide = result.hide || false; - for (var ei in dependencies[dependon][condition][value]) { - var eltolock = dependencies[dependon][condition][value][ei]; - if (({}).hasOwnProperty.call(tohide, eltolock)) { - tohide[eltolock] = tohide[eltolock] || hide; + for (isHide in dependencies[dependon][condition][value]) { + checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1); + if (Y.Lang.isFunction(this[checkfunction])) { + result = this[checkfunction].apply(this, [elements, value, (isHide === "1"), e]); } else { - tohide[eltolock] = hide; + result = this._dependencyDefault(elements, value, (isHide === "1"), e); } + lock = result.lock || false; + hide = result.hide || false; + for (var ei in dependencies[dependon][condition][value][isHide]) { + var eltolock = dependencies[dependon][condition][value][isHide][ei]; + if (({}).hasOwnProperty.call(tohide, eltolock)) { + tohide[eltolock] = tohide[eltolock] || hide; + } else { + tohide[eltolock] = hide; + } - if (({}).hasOwnProperty.call(tolock, eltolock)) { - tolock[eltolock] = tolock[eltolock] || lock; - } else { - tolock[eltolock] = lock; + if (({}).hasOwnProperty.call(tolock, eltolock)) { + tolock[eltolock] = tolock[eltolock] || lock; + } else { + tolock[eltolock] = lock; + } } } } @@ -260,8 +271,13 @@ if (typeof M.form.dependencyManager === 'undefined') { _hideElement: function(name, hidden) { var els = this.elementsByName(name); els.each(function(node) { - var e = node.ancestor('.fitem'); + var e = node.ancestor('.fitem', true); if (e) { + if (hidden) { + e.setAttribute('hidden', 'hidden'); + } else { + e.removeAttribute('hidden'); + } e.setStyles({ display: (hidden) ? 'none' : '' }); @@ -291,7 +307,7 @@ if (typeof M.form.dependencyManager === 'undefined') { return false; }, - _dependencyNotchecked: function(elements, value) { + _dependencyNotchecked: function(elements, value, isHide) { var lock = false; elements.each(function() { if (this.getAttribute('type').toLowerCase() == 'hidden' && @@ -306,10 +322,10 @@ if (typeof M.form.dependencyManager === 'undefined') { }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; }, - _dependencyChecked: function(elements, value) { + _dependencyChecked: function(elements, value, isHide) { var lock = false; elements.each(function() { if (this.getAttribute('type').toLowerCase() == 'hidden' && @@ -324,20 +340,20 @@ if (typeof M.form.dependencyManager === 'undefined') { }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; }, - _dependencyNoitemselected: function(elements, value) { + _dependencyNoitemselected: function(elements, value, isHide) { var lock = false; elements.each(function() { lock = lock || this.get('selectedIndex') == -1; }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; }, - _dependencyEq: function(elements, value) { + _dependencyEq: function(elements, value, isHide) { var lock = false; var hiddenVal = false; var options, v, selected, values; @@ -391,7 +407,7 @@ if (typeof M.form.dependencyManager === 'undefined') { }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; }, /** @@ -402,7 +418,7 @@ if (typeof M.form.dependencyManager === 'undefined') { * @returns {{lock: boolean, hide: boolean}} * @private */ - _dependencyIn: function(elements, values) { + _dependencyIn: function(elements, values, isHide) { // A pipe (|) is used as a value separator // when multiple values have to be passed on at the same time. values = values.split('|'); @@ -458,7 +474,7 @@ if (typeof M.form.dependencyManager === 'undefined') { }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; }, _dependencyHide: function(elements, value) { @@ -467,7 +483,7 @@ if (typeof M.form.dependencyManager === 'undefined') { hide: true }; }, - _dependencyDefault: function(elements, value, ev) { + _dependencyDefault: function(elements, value, isHide) { var lock = false, hiddenVal = false, values @@ -521,7 +537,7 @@ if (typeof M.form.dependencyManager === 'undefined') { }); return { lock: lock, - hide: false + hide: isHide ? lock : false }; } }, { diff --git a/lib/form/group.php b/lib/form/group.php index da9359ddbea..f4f241fd045 100644 --- a/lib/form/group.php +++ b/lib/form/group.php @@ -213,6 +213,7 @@ class MoodleQuickForm_group extends HTML_QuickForm_group implements templatable $i++; } + $context['groupname'] = $name; $context['elements'] = $elements; return $context; } diff --git a/lib/form/templates/element-template.mustache b/lib/form/templates/element-template.mustache index 9463064f2e9..cc1195ad53c 100644 --- a/lib/form/templates/element-template.mustache +++ b/lib/form/templates/element-template.mustache @@ -46,7 +46,7 @@ } }} -
+
{{{ helpbutton }}} diff --git a/lib/form/tests/behat/hideif.feature b/lib/form/tests/behat/hideif.feature new file mode 100644 index 00000000000..04027bacd1b --- /dev/null +++ b/lib/form/tests/behat/hideif.feature @@ -0,0 +1,29 @@ +@core @javascript @core_form +Feature: hideIf functionality in forms + For forms including hideIf functions + As a user + If I trigger the hideIf condition then the form elements will be hidden + + Background: + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And I log in as "admin" + And I am on "Course 1" course homepage + And I turn editing mode on + + Scenario: When 'eq' hideIf conditions are not met, the relevant elements are shown + When I add a "Assignment" to section "1" + And I expand all fieldsets + And I set the field "Students submit in groups" to "Yes" + Then I should see "Require group to make submission" + And I should see "Require all group members submit" + And I should see "Grouping for student groups" + + Scenario: When 'eq' hideIf conditions are met, the relevant elements are hidden + When I add a "Assignment" to section "1" + And I expand all fieldsets + And I set the field "Students submit in groups" to "No" + Then I should not see "Require group to make submission" + And I should not see "Require all group members to submit" + And I should not see "Grouping for student groups" diff --git a/lib/formslib.php b/lib/formslib.php index 965b0a4f51a..66527b655b3 100644 --- a/lib/formslib.php +++ b/lib/formslib.php @@ -1412,6 +1412,11 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless { /** @var array dependent state for the element/'s */ var $_dependencies = array(); + /** + * @var array elements that will become hidden based on another element + */ + protected $_hideifs = array(); + /** @var array Array of buttons that if pressed do not result in the processing of the form. */ var $_noSubmitButtons=array(); @@ -1460,6 +1465,16 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless { */ protected $clientvalidation = false; + /** + * Is this a 'disableIf' dependency ? + */ + const DEP_DISABLE = 0; + + /** + * Is this a 'hideIf' dependency? + */ + const DEP_HIDE = 1; + /** * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless * @@ -2426,8 +2441,7 @@ require(["core/event", "jquery"], function(Event, $) { foreach ($conditions as $condition=>$values) { $result[$dependentOn][$condition] = array(); foreach ($values as $value=>$dependents) { - $result[$dependentOn][$condition][$value] = array(); - $i = 0; + $result[$dependentOn][$condition][$value][self::DEP_DISABLE] = array(); foreach ($dependents as $dependent) { $elements = $this->_getElNamesRecursive($dependent); if (empty($elements)) { @@ -2438,7 +2452,29 @@ require(["core/event", "jquery"], function(Event, $) { if ($element == $dependentOn) { continue; } - $result[$dependentOn][$condition][$value][] = $element; + $result[$dependentOn][$condition][$value][self::DEP_DISABLE][] = $element; + } + } + } + } + } + foreach ($this->_hideifs as $dependenton => $conditions) { + $result[$dependenton] = array(); + foreach ($conditions as $condition => $values) { + $result[$dependenton][$condition] = array(); + foreach ($values as $value => $dependents) { + $result[$dependenton][$condition][$value][self::DEP_HIDE] = array(); + foreach ($dependents as $dependent) { + $elements = $this->_getElNamesRecursive($dependent); + if (!in_array($dependent, $elements)) { + // Always want to hide the main element, even if it contains sub-elements as well. + $elements[] = $dependent; + } + foreach ($elements as $element) { + if ($element == $dependenton) { + continue; + } + $result[$dependenton][$condition][$value][self::DEP_HIDE][] = $element; } } } @@ -2533,6 +2569,40 @@ require(["core/event", "jquery"], function(Event, $) { $this->_dependencies[$dependentOn][$condition][$value][] = $elementName; } + /** + * Adds a dependency for $elementName which will be hidden if $condition is met. + * If $condition = 'notchecked' (default) then the condition is that the $dependentOn element + * is not checked. If $condition = 'checked' then the condition is that the $dependentOn element + * is checked. If $condition is something else (like "eq" for equals) then it is checked to see if the value + * of the $dependentOn element is $condition (such as equal) to $value. + * + * When working with multiple selects, the dependentOn has to be the real name of the select, meaning that + * it will most likely end up with '[]'. Also, the value should be an array of required values, or a string + * containing the values separated by pipes: array('red', 'blue') or 'red|blue'. + * + * @param string $elementname the name of the element which will be hidden + * @param string $dependenton the name of the element whose state will be checked for condition + * @param string $condition the condition to check + * @param mixed $value used in conjunction with condition. + */ + public function hideIf($elementname, $dependenton, $condition = 'notchecked', $value = '1') { + // Multiple selects allow for a multiple selection, we transform the array to string here as + // an array cannot be used as a key in an associative array. + if (is_array($value)) { + $value = implode('|', $value); + } + if (!array_key_exists($dependenton, $this->_hideifs)) { + $this->_hideifs[$dependenton] = array(); + } + if (!array_key_exists($condition, $this->_hideifs[$dependenton])) { + $this->_hideifs[$dependenton][$condition] = array(); + } + if (!array_key_exists($value, $this->_hideifs[$dependenton][$condition])) { + $this->_hideifs[$dependenton][$condition][$value] = array(); + } + $this->_hideifs[$dependenton][$condition][$value][] = $elementname; + } + /** * Registers button as no submit button * diff --git a/mod/assign/mod_form.php b/mod/assign/mod_form.php index 7f88c0738de..ffb9a159402 100644 --- a/mod/assign/mod_form.php +++ b/mod/assign/mod_form.php @@ -148,12 +148,12 @@ class mod_assign_mod_form extends moodleform_mod { 'preventsubmissionnotingroup', 'assign'); $mform->setType('preventsubmissionnotingroup', PARAM_BOOL); - $mform->disabledIf('preventsubmissionnotingroup', 'teamsubmission', 'eq', 0); + $mform->hideIf('preventsubmissionnotingroup', 'teamsubmission', 'eq', 0); $name = get_string('requireallteammemberssubmit', 'assign'); $mform->addElement('selectyesno', 'requireallteammemberssubmit', $name); $mform->addHelpButton('requireallteammemberssubmit', 'requireallteammemberssubmit', 'assign'); - $mform->disabledIf('requireallteammemberssubmit', 'teamsubmission', 'eq', 0); + $mform->hideIf('requireallteammemberssubmit', 'teamsubmission', 'eq', 0); $mform->disabledIf('requireallteammemberssubmit', 'submissiondrafts', 'eq', 0); $groupings = groups_get_all_groupings($assignment->get_course()->id); @@ -166,7 +166,7 @@ class mod_assign_mod_form extends moodleform_mod { $name = get_string('teamsubmissiongroupingid', 'assign'); $mform->addElement('select', 'teamsubmissiongroupingid', $name, $options); $mform->addHelpButton('teamsubmissiongroupingid', 'teamsubmissiongroupingid', 'assign'); - $mform->disabledIf('teamsubmissiongroupingid', 'teamsubmission', 'eq', 0); + $mform->hideIf('teamsubmissiongroupingid', 'teamsubmission', 'eq', 0); if ($assignment->has_submissions_or_grades()) { $mform->freeze('teamsubmissiongroupingid'); } diff --git a/theme/boost/templates/core_form/element-template-inline.mustache b/theme/boost/templates/core_form/element-template-inline.mustache index 88593fccdcb..95f1fa79c87 100644 --- a/theme/boost/templates/core_form/element-template-inline.mustache +++ b/theme/boost/templates/core_form/element-template-inline.mustache @@ -1,4 +1,4 @@ -
+