MDL-53848 form: add hideIf functionality
authorDavo Smith <git@davosmith.co.uk>
Mon, 19 Dec 2016 11:36:54 +0000 (11:36 +0000)
committerDavo Smith <davo.smith@synergy-learning.com>
Thu, 17 Aug 2017 13:52:10 +0000 (14:52 +0100)
lib/form/form.js
lib/form/tests/behat/hideif.feature [new file with mode: 0644]
lib/form/tests/fixtures/formhideiftestpage.php [new file with mode: 0644]
lib/formslib.php

index 38e518b..13464fb 100644 (file)
@@ -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();
+                            }
                         }
                     }
                 }
@@ -118,7 +120,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 +128,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, e]);
                         } else {
-                            tohide[eltolock] = hide;
+                            result = this._dependencyDefault(elements, value, !!isHide, 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;
+                            }
                         }
                     }
                 }
@@ -291,7 +295,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 +310,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 +328,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 +395,7 @@ if (typeof M.form.dependencyManager === 'undefined') {
             });
             return {
                 lock: lock,
-                hide: false
+                hide: isHide ? lock : false
             };
         },
         /**
@@ -402,7 +406,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 +462,7 @@ if (typeof M.form.dependencyManager === 'undefined') {
             });
             return {
                 lock: lock,
-                hide: false
+                hide: isHide ? lock : false
             };
         },
         _dependencyHide: function(elements, value) {
@@ -467,7 +471,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 +525,7 @@ if (typeof M.form.dependencyManager === 'undefined') {
             });
             return {
                 lock: lock,
-                hide: false
+                hide: isHide ? lock : false
             };
         }
     }, {
diff --git a/lib/form/tests/behat/hideif.feature b/lib/form/tests/behat/hideif.feature
new file mode 100644 (file)
index 0000000..864216a
--- /dev/null
@@ -0,0 +1,52 @@
+@core @javascript
+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 "activities" exist:
+      | activity | name | intro                                                                   | course               | section | idnumber |
+      | label    | L1   | <a href="lib/form/tests/fixtures/formhideiftestpage.php">HideIfLink</a> | Acceptance test site | 1       | L1       |
+    And I am on site homepage
+    And I follow "HideIfLink"
+
+  Scenario: When 'eq' hideIf conditions are not met, the relevant elements are shown
+    When I set the field "Select yesno example" to "Yes"
+    Then I should see "Test eq hideif"
+    And "#id_testeqhideif" "css_element" should be visible
+
+  Scenario: When 'eq' hideIf conditions are met, the relevant elements are hidden
+    When I set the field "Select yesno example" to "No"
+    Then I should not see "Test eq hideif"
+    And "#id_testeqhideif" "css_element" should not be visible
+
+  Scenario: When 'checked' hideIf conditions are not met, the relevant elements are shown
+    When I set the field "Checkbox example" to "0"
+    Then I should see "Test checked hideif"
+    And "#id_testcheckedhideif" "css_element" should be visible
+
+  Scenario: When 'checked' hideIf conditions are met, the relevant elements are hidden
+    When I set the field "Checkbox example" to "1"
+    Then I should not see "Test checked hideif"
+    And "#id_testcheckedhideif" "css_element" should not be visible
+
+  Scenario: When 'notchecked' hideIf conditions are not met, the relevant elements are shown
+    When I set the field "Checkbox example" to "1"
+    Then I should see "Test not checked hideif"
+    And "#id_testnotcheckedhideif" "css_element" should be visible
+
+  Scenario: When 'notchecked' hideIf conditions are met, the relevant elements are hidden
+    When I set the field "Checkbox example" to "0"
+    Then I should not see "Test not checked hideif"
+    And "#id_testnotcheckedhideif" "css_element" should not be visible
+
+  Scenario: When 'in' hideIf conditions are not met, the relevant elements are shown
+    When I set the field "Select example" to "3"
+    Then I should see "Test in hideif"
+    And "#id_testinhideif" "css_element" should be visible
+
+  Scenario: When 'in' hideIf conditions are met, the relevant elements are hidden
+    When I set the field "Select example" to "2"
+    Then I should not see "Test in hideif"
+    And "#id_testinhideif" "css_element" should not be visible
diff --git a/lib/form/tests/fixtures/formhideiftestpage.php b/lib/form/tests/fixtures/formhideiftestpage.php
new file mode 100644 (file)
index 0000000..86a856e
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+
+/**
+ * To support behat tests for hideif functionality (which is not yet used by any core forms).
+ *
+ * @package   core
+ * @copyright 2016 Davo Smith, Synergy Learning
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(__FILE__).'/../../../../config.php');
+global $CFG, $PAGE, $OUTPUT;
+require_once($CFG->libdir.'/formslib.php');
+
+// Behat test fixture only.
+defined('BEHAT_SITE_RUNNING') || die('Only available on Behat test server');
+
+/**
+ * Class hideif_form
+ * @copyright 2016 Davo Smith, Synergy Learning
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class hideif_form extends moodleform {
+
+    /**
+     * Form definition.
+     */
+    protected function definition() {
+        $mform = $this->_form;
+
+        // Use 'selectyesno' to show/hide element.
+        $mform->addElement('selectyesno', 'selectyesnoexample', 'Select yesno example');
+        $mform->setDefault('selectyesnoexample', 0);
+
+        $mform->addElement('text', 'testeqhideif', 'Test eq hideif');
+        $mform->setType('testeqhideif', PARAM_TEXT);
+        $mform->hideIf('testeqhideif', 'selectyesnoexample', 'eq', 0);
+
+        // Use 'checkbox' to show/hide element.
+        $mform->addElement('advcheckbox', 'checkboxexample', 'Checkbox example');
+        $mform->setDefault('checkboxexample', 0);
+
+        $mform->addElement('text', 'testcheckedhideif', 'Test checked hideif');
+        $mform->setType('testcheckedhideif', PARAM_TEXT);
+        $mform->hideIf('testcheckedhideif', 'checkboxexample', 'checked');
+
+        $mform->addElement('text', 'testnotcheckedhideif', 'Test not checked hideif');
+        $mform->setType('testnotcheckedhideif', PARAM_TEXT);
+        $mform->hideIf('testnotcheckedhideif', 'checkboxexample', 'notchecked');
+
+        // Use 'select' to show/hide element.
+        $opts = [1, 2, 3, 4, 5];
+        $opts = array_combine($opts, $opts);
+        $mform->addElement('select', 'selectexample', 'Select example', $opts);
+        $mform->setDefault('selectexample', 1);
+
+        $mform->addElement('text', 'testinhideif', 'Test in hideif');
+        $mform->setType('testinhideif', PARAM_TEXT);
+        $mform->hideIf('testinhideif', 'selectexample', 'in', [1, 2, 5]);
+    }
+}
+
+$PAGE->set_url('/lib/tests/fixtures/form_hideif.php');
+$PAGE->set_context(context_system::instance());
+$form = new hideif_form();
+
+echo $OUTPUT->header();
+$form->display();
+echo $OUTPUT->footer();
index 965b0a4..5c41ad2 100644 (file)
@@ -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,12 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
      */
     protected $clientvalidation = false;
 
+    /**
+     * Is this a 'disableIf' dependency of a 'hideIf' dependency?
+     */
+    const DEP_DISABLE = 0;
+    const DEP_HIDE = 1;
+
     /**
      * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless
      *
@@ -2426,8 +2437,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 +2448,29 @@ require(["core/event", "jquery"], function(Event, $) {
                             if ($element == $dependentOn) {
                                 continue;
                             }
-                            $result[$dependentOn][$condition][$value][] = $element;
+                            $result[$dependentOn][$condition][$value][0][] = $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 (empty($elements)) {
+                            // Probably element inside of some group.
+                            $elements = array($dependent);
+                        }
+                        foreach ($elements as $element) {
+                            if ($element == $dependenton) {
+                                continue;
+                            }
+                            $result[$dependenton][$condition][$value][1][] = $element;
                         }
                     }
                 }
@@ -2533,6 +2565,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
      *