From dd633f169116d3402961a6a68025d5fdbbe9149c Mon Sep 17 00:00:00 2001 From: Lars Bonczek Date: Fri, 13 Dec 2024 13:29:10 +0100 Subject: [PATCH] MDL-83563 form: Fix hideIf and disabledIf dependent on multiselects --- lib/amd/build/showhidesettings.min.js | 2 +- lib/amd/build/showhidesettings.min.js.map | 2 +- lib/amd/src/showhidesettings.js | 6 +- lib/behat/form_field/behat_form_select.php | 7 ++ lib/form/form.js | 76 ++++++++------- lib/form/tests/behat/disabledif.feature | 20 ++++ lib/form/tests/behat/hideif.feature | 20 ++++ .../multiselect_hideif_disabledif_form.php | 96 +++++++++++++++++++ ...multiselect_hide_if_admin_settingspage.php | 78 +++++++++++++++ .../behat/hide_if_admin_settings.feature | 28 ++++++ 10 files changed, 298 insertions(+), 37 deletions(-) create mode 100644 lib/form/tests/fixtures/multiselect_hideif_disabledif_form.php create mode 100644 lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php create mode 100644 lib/tests/behat/hide_if_admin_settings.feature diff --git a/lib/amd/build/showhidesettings.min.js b/lib/amd/build/showhidesettings.min.js index 6ddcd8e5dce..0563c5c4209 100644 --- a/lib/amd/build/showhidesettings.min.js +++ b/lib/amd/build/showhidesettings.min.js @@ -4,6 +4,6 @@ * @copyright 2018 Davo Smith, Synergy Learning * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core/showhidesettings",["jquery"],(function($){var dependencies;function isCheckboxHiddenElement($el){return $el.is("input[type=hidden]")&&$el.siblings('input[type=checkbox][name="'+$el.attr("name")+'"]').length}function isCheckedRelevant($el,value){return!isCheckboxHiddenElement($el)&&!function($el,value){return $el.is("input[type=radio]")&&$el.attr("value")!==value}($el,value)}function isUncheckedRadioButton($el){return $el.is("input[type=radio]")&&!$el.prop("checked")}function isUncheckedCheckbox($el){return $el.is("input[type=checkbox]")&&!$el.prop("checked")}function isMultiSelect($el){return $el.is("select")&&$el.prop("multiple")}function multiSelectMatches($el,values){var selected=$el.val()||[];if(!values.length)return!1;if(selected.length!==values.length)return!1;for(var i in selected)if(selected.hasOwnProperty(i)&&-1===values.indexOf(selected[i]))return!1;return!0}var depFns={notchecked:function($dependon,value){var hide=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);isCheckedRelevant($el,value)&&(hide=hide||!$el.prop("checked"))})),hide},checked:function($dependon,value){var hide=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);isCheckedRelevant($el,value)&&(hide=hide||$el.prop("checked"))})),hide},noitemselected:function($dependon){var hide=!1;return $dependon.each((function(idx,el){var $el=$(el);hide=hide||-1===$el.prop("selectedIndex")})),hide},eq:function($dependon,value){var hide=!1,hiddenVal=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);if(!isUncheckedRadioButton($el))if(isCheckboxHiddenElement($el))hiddenVal=$el.val()===value;else if(isUncheckedCheckbox($el))hide=hide||hiddenVal;else if(isMultiSelect($el)){var values=value.split("|");hide=multiSelectMatches($el,values)}else hide=hide||$el.val()===value})),hide},in:function($dependon,value){var hide=!1,hiddenVal=!1,values=value.split("|");return $dependon.each((function(idx,el){var $el=$(el);isUncheckedRadioButton($el)||(isCheckboxHiddenElement($el)?hiddenVal=values.indexOf($el.val())>-1:hide=isUncheckedCheckbox($el)?hide||hiddenVal:isMultiSelect($el)?multiSelectMatches($el,values):hide||values.indexOf($el.val())>-1)})),hide},defaultCondition:function($dependon,value){var hide=!1,hiddenVal=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);if(!isUncheckedRadioButton($el))if(isCheckboxHiddenElement($el))hiddenVal=$el.val()!==value;else if(isUncheckedCheckbox($el))hide=hide||hiddenVal;else if(isMultiSelect($el)){var values=value.split("|");hide=!multiSelectMatches($el,values)}else hide=hide||$el.val()!==value})),hide}};function getElementsByName(name){return $('[name="'+name+'"],[name^="'+name+'["]')}function updateDependencies(){var toHide={};$.each(dependencies,(function(dependonname){var dependon=getElementsByName(dependonname);$.each(dependencies[dependonname],(function(condition,values){$.each(values,(function(value,elements){var hide=function($dependon,condition,value){return"function"==typeof depFns[condition]?depFns[condition]($dependon,value):depFns.defaultCondition($dependon,value)}(dependon,condition,value);$.each(elements,(function(idx,elToHide){toHide.hasOwnProperty(elToHide)?toHide[elToHide]=toHide[elToHide]||hide:toHide[elToHide]=hide}))}))}))})),$.each(toHide,(function(elToHide,hide){getElementsByName(elToHide).each((function(idx,el){var $parent=$(el).closest(".form-item");$parent.length&&(hide?$parent.hide():$parent.show())}))}))}return{init:function(opts){dependencies=opts.dependencies,$.each(dependencies,(function(depname){var $el=getElementsByName(depname);$el.length&&$el.on("change",updateDependencies)})),updateDependencies(),$(".form-dependenton").hide()}}})); +define("core/showhidesettings",["jquery"],(function($){var dependencies;function isCheckboxHiddenElement($el){return $el.is("input[type=hidden]")&&$el.siblings('input[type=checkbox][name="'+$el.attr("name")+'"]').length}function isCheckedRelevant($el,value){return!isCheckboxHiddenElement($el)&&!function($el,value){return $el.is("input[type=radio]")&&$el.attr("value")!==value}($el,value)}function isUncheckedRadioButton($el){return $el.is("input[type=radio]")&&!$el.prop("checked")}function isUncheckedCheckbox($el){return $el.is("input[type=checkbox]")&&!$el.prop("checked")}function isMultiSelect($el){return $el.is("select")&&$el.prop("multiple")}function multiSelectMatches($el,values){var selected=$el.val()||[];if(1===values.length&&""===values[0])return 0===selected.length;if(selected.length!==values.length)return!1;for(var i in selected)if(selected.hasOwnProperty(i)&&-1===values.indexOf(selected[i]))return!1;return!0}var depFns={notchecked:function($dependon,value){var hide=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);isCheckedRelevant($el,value)&&(hide=hide||!$el.prop("checked"))})),hide},checked:function($dependon,value){var hide=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);isCheckedRelevant($el,value)&&(hide=hide||$el.prop("checked"))})),hide},noitemselected:function($dependon){var hide=!1;return $dependon.each((function(idx,el){var $el=$(el);hide=hide||-1===$el.prop("selectedIndex")})),hide},eq:function($dependon,value){var hide=!1,hiddenVal=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);if(!isUncheckedRadioButton($el))if(isCheckboxHiddenElement($el))hiddenVal=$el.val()===value;else if(isUncheckedCheckbox($el))hide=hide||hiddenVal;else if(isMultiSelect($el)){var values=value.split("|");hide=multiSelectMatches($el,values)}else hide=hide||$el.val()===value})),hide},in:function($dependon,value){var hide=!1,hiddenVal=!1,values=value.split("|");return $dependon.each((function(idx,el){var $el=$(el);isUncheckedRadioButton($el)||(isCheckboxHiddenElement($el)?hiddenVal=values.indexOf($el.val())>-1:hide=isUncheckedCheckbox($el)?hide||hiddenVal:isMultiSelect($el)?multiSelectMatches($el,values):hide||values.indexOf($el.val())>-1)})),hide},defaultCondition:function($dependon,value){var hide=!1,hiddenVal=!1;return value=String(value),$dependon.each((function(idx,el){var $el=$(el);if(!isUncheckedRadioButton($el))if(isCheckboxHiddenElement($el))hiddenVal=$el.val()!==value;else if(isUncheckedCheckbox($el))hide=hide||hiddenVal;else if(isMultiSelect($el)){var values=value.split("|");hide=!multiSelectMatches($el,values)}else hide=hide||$el.val()!==value})),hide}};function getElementsByName(name){return $('[name="'+name+'"],[name^="'+name+'["]')}function updateDependencies(){var toHide={};$.each(dependencies,(function(dependonname){var dependon=getElementsByName(dependonname);$.each(dependencies[dependonname],(function(condition,values){$.each(values,(function(value,elements){var hide=function($dependon,condition,value){return"function"==typeof depFns[condition]?depFns[condition]($dependon,value):depFns.defaultCondition($dependon,value)}(dependon,condition,value);$.each(elements,(function(idx,elToHide){toHide.hasOwnProperty(elToHide)?toHide[elToHide]=toHide[elToHide]||hide:toHide[elToHide]=hide}))}))}))})),$.each(toHide,(function(elToHide,hide){getElementsByName(elToHide).each((function(idx,el){var $parent=$(el).closest(".form-item");$parent.length&&(hide?$parent.hide():$parent.show())}))}))}return{init:function(opts){dependencies=opts.dependencies,$.each(dependencies,(function(depname){var $el=getElementsByName(depname);$el.length&&$el.on("change",updateDependencies)})),updateDependencies(),$(".form-dependenton").hide()}}})); //# sourceMappingURL=showhidesettings.min.js.map \ No newline at end of file diff --git a/lib/amd/build/showhidesettings.min.js.map b/lib/amd/build/showhidesettings.min.js.map index 1fc134ef5ae..bc630b41cc1 100644 --- a/lib/amd/build/showhidesettings.min.js.map +++ b/lib/amd/build/showhidesettings.min.js.map @@ -1 +1 @@ -{"version":3,"file":"showhidesettings.min.js","sources":["../src/showhidesettings.js"],"sourcesContent":["/**\n * Show/hide admin settings based on other settings selected\n *\n * @copyright 2018 Davo Smith, Synergy Learning\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n var dependencies;\n\n // -------------------------------------------------\n // Support functions, used by dependency functions.\n // -------------------------------------------------\n\n /**\n * Check to see if the given element is the hidden element that makes sure checkbox\n * elements always submit a value.\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isCheckboxHiddenElement($el) {\n return ($el.is('input[type=hidden]') && $el.siblings('input[type=checkbox][name=\"' + $el.attr('name') + '\"]').length);\n }\n\n /**\n * Check to see if this is a radio button with the wrong value (i.e. a radio button from\n * the group we are interested in, but not the specific one we wanted).\n * @param {jQuery} $el\n * @param {string} value\n * @returns {boolean}\n */\n function isWrongRadioButton($el, value) {\n return ($el.is('input[type=radio]') && $el.attr('value') !== value);\n }\n\n /**\n * Is this element relevant when we're looking for checked / not checked status?\n * @param {jQuery} $el\n * @param {string} value\n * @returns {boolean}\n */\n function isCheckedRelevant($el, value) {\n return (!isCheckboxHiddenElement($el) && !isWrongRadioButton($el, value));\n }\n\n /**\n * Is this an unchecked radio button? (If it is, we want to skip it, as\n * we're only interested in the value of the radio button that is checked)\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isUncheckedRadioButton($el) {\n return ($el.is('input[type=radio]') && !$el.prop('checked'));\n }\n\n /**\n * Is this an unchecked checkbox?\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isUncheckedCheckbox($el) {\n return ($el.is('input[type=checkbox]') && !$el.prop('checked'));\n }\n\n /**\n * Is this a multi-select select element?\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isMultiSelect($el) {\n return ($el.is('select') && $el.prop('multiple'));\n }\n\n /**\n * Does the multi-select exactly match the list of values provided?\n * @param {jQuery} $el\n * @param {array} values\n * @returns {boolean}\n */\n function multiSelectMatches($el, values) {\n var selected = $el.val() || [];\n if (!values.length) {\n // No values - nothing to match against.\n return false;\n }\n if (selected.length !== values.length) {\n // Different number of expected and actual values - cannot possibly be a match.\n return false;\n }\n for (var i in selected) {\n if (selected.hasOwnProperty(i)) {\n if (values.indexOf(selected[i]) === -1) {\n return false; // Found a non-matching value - give up immediately.\n }\n }\n }\n // Didn't find a non-matching value, so we have a match.\n return true;\n }\n\n // -------------------------------\n // Specific dependency functions.\n // -------------------------------\n\n var depFns = {\n notchecked: function($dependon, value) {\n var hide = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isCheckedRelevant($el, value)) {\n hide = hide || !$el.prop('checked');\n }\n });\n return hide;\n },\n\n checked: function($dependon, value) {\n var hide = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isCheckedRelevant($el, value)) {\n hide = hide || $el.prop('checked');\n }\n });\n return hide;\n },\n\n noitemselected: function($dependon) {\n var hide = false;\n $dependon.each(function(idx, el) {\n var $el = $(el);\n hide = hide || ($el.prop('selectedIndex') === -1);\n });\n return hide;\n },\n\n eq: function($dependon, value) {\n var hide = false;\n var hiddenVal = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = ($el.val() === value);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // Expect a list of values to match, separated by '|' - all of them must\n // match the values selected.\n var values = value.split('|');\n hide = multiSelectMatches($el, values);\n return;\n }\n // All other element types - just compare the value directly.\n hide = hide || ($el.val() === value);\n });\n return hide;\n },\n\n 'in': function($dependon, value) {\n var hide = false;\n var hiddenVal = false;\n var values = value.split('|');\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = (values.indexOf($el.val()) > -1);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // For multiselect, we check to see if the list of values provided matches the list selected.\n hide = multiSelectMatches($el, values);\n return;\n }\n // All other element types - check to see if the value is in the list.\n hide = hide || (values.indexOf($el.val()) > -1);\n });\n return hide;\n },\n\n defaultCondition: function($dependon, value) { // Not equal.\n var hide = false;\n var hiddenVal = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = ($el.val() !== value);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // Expect a list of values to match, separated by '|' - all of them must\n // match the values selected to *not* hide the element.\n var values = value.split('|');\n hide = !multiSelectMatches($el, values);\n return;\n }\n // All other element types - just compare the value directly.\n hide = hide || ($el.val() !== value);\n });\n return hide;\n }\n };\n\n /**\n * Find the element with the given name\n * @param {String} name\n * @returns {*|jQuery|HTMLElement}\n */\n function getElementsByName(name) {\n // For the array elements, we use [name^=\"something[\"] to find the elements that their name begins with 'something['/\n // This is to find both name = 'something[]' and name='something[index]'.\n return $('[name=\"' + name + '\"],[name^=\"' + name + '[\"]');\n }\n\n /**\n * Check to see whether a particular condition is met\n * @param {*|jQuery|HTMLElement} $dependon\n * @param {String} condition\n * @param {mixed} value\n * @returns {Boolean}\n */\n function checkDependency($dependon, condition, value) {\n if (typeof depFns[condition] === \"function\") {\n return depFns[condition]($dependon, value);\n }\n return depFns.defaultCondition($dependon, value);\n }\n\n /**\n * Show / hide the elements that depend on some elements.\n */\n function updateDependencies() {\n // Process all dependency conditions.\n var toHide = {};\n $.each(dependencies, function(dependonname) {\n var dependon = getElementsByName(dependonname);\n $.each(dependencies[dependonname], function(condition, values) {\n $.each(values, function(value, elements) {\n var hide = checkDependency(dependon, condition, value);\n $.each(elements, function(idx, elToHide) {\n if (toHide.hasOwnProperty(elToHide)) {\n toHide[elToHide] = toHide[elToHide] || hide;\n } else {\n toHide[elToHide] = hide;\n }\n });\n });\n });\n });\n\n // Update the hidden status of all relevant elements.\n $.each(toHide, function(elToHide, hide) {\n getElementsByName(elToHide).each(function(idx, el) {\n var $parent = $(el).closest('.form-item');\n if ($parent.length) {\n if (hide) {\n $parent.hide();\n } else {\n $parent.show();\n }\n }\n });\n });\n }\n\n /**\n * Initialise the event handlers.\n */\n function initHandlers() {\n $.each(dependencies, function(depname) {\n var $el = getElementsByName(depname);\n if ($el.length) {\n $el.on('change', updateDependencies);\n }\n });\n updateDependencies();\n }\n\n /**\n * Hide the 'this setting may be hidden' messages.\n */\n function hideDependencyInfo() {\n $('.form-dependenton').hide();\n }\n\n return {\n init: function(opts) {\n dependencies = opts.dependencies;\n initHandlers();\n hideDependencyInfo();\n }\n };\n});"],"names":["define","$","dependencies","isCheckboxHiddenElement","$el","is","siblings","attr","length","isCheckedRelevant","value","isWrongRadioButton","isUncheckedRadioButton","prop","isUncheckedCheckbox","isMultiSelect","multiSelectMatches","values","selected","val","i","hasOwnProperty","indexOf","depFns","notchecked","$dependon","hide","String","each","idx","el","checked","noitemselected","eq","hiddenVal","split","defaultCondition","getElementsByName","name","updateDependencies","toHide","dependonname","dependon","condition","elements","checkDependency","elToHide","$parent","closest","show","init","opts","depname","on"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"showhidesettings.min.js","sources":["../src/showhidesettings.js"],"sourcesContent":["/**\n * Show/hide admin settings based on other settings selected\n *\n * @copyright 2018 Davo Smith, Synergy Learning\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n var dependencies;\n\n // -------------------------------------------------\n // Support functions, used by dependency functions.\n // -------------------------------------------------\n\n /**\n * Check to see if the given element is the hidden element that makes sure checkbox\n * elements always submit a value.\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isCheckboxHiddenElement($el) {\n return ($el.is('input[type=hidden]') && $el.siblings('input[type=checkbox][name=\"' + $el.attr('name') + '\"]').length);\n }\n\n /**\n * Check to see if this is a radio button with the wrong value (i.e. a radio button from\n * the group we are interested in, but not the specific one we wanted).\n * @param {jQuery} $el\n * @param {string} value\n * @returns {boolean}\n */\n function isWrongRadioButton($el, value) {\n return ($el.is('input[type=radio]') && $el.attr('value') !== value);\n }\n\n /**\n * Is this element relevant when we're looking for checked / not checked status?\n * @param {jQuery} $el\n * @param {string} value\n * @returns {boolean}\n */\n function isCheckedRelevant($el, value) {\n return (!isCheckboxHiddenElement($el) && !isWrongRadioButton($el, value));\n }\n\n /**\n * Is this an unchecked radio button? (If it is, we want to skip it, as\n * we're only interested in the value of the radio button that is checked)\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isUncheckedRadioButton($el) {\n return ($el.is('input[type=radio]') && !$el.prop('checked'));\n }\n\n /**\n * Is this an unchecked checkbox?\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isUncheckedCheckbox($el) {\n return ($el.is('input[type=checkbox]') && !$el.prop('checked'));\n }\n\n /**\n * Is this a multi-select select element?\n * @param {jQuery} $el\n * @returns {boolean}\n */\n function isMultiSelect($el) {\n return ($el.is('select') && $el.prop('multiple'));\n }\n\n /**\n * Does the multi-select exactly match the list of values provided?\n * @param {jQuery} $el\n * @param {array} values\n * @returns {boolean}\n */\n function multiSelectMatches($el, values) {\n var selected = $el.val() || [];\n if (values.length === 1 && values[0] === '') {\n // Values array contains a single empty entry -> value was empty.\n return selected.length === 0;\n }\n if (selected.length !== values.length) {\n // Different number of expected and actual values - cannot possibly be a match.\n return false;\n }\n for (var i in selected) {\n if (selected.hasOwnProperty(i)) {\n if (values.indexOf(selected[i]) === -1) {\n return false; // Found a non-matching value - give up immediately.\n }\n }\n }\n // Didn't find a non-matching value, so we have a match.\n return true;\n }\n\n // -------------------------------\n // Specific dependency functions.\n // -------------------------------\n\n var depFns = {\n notchecked: function($dependon, value) {\n var hide = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isCheckedRelevant($el, value)) {\n hide = hide || !$el.prop('checked');\n }\n });\n return hide;\n },\n\n checked: function($dependon, value) {\n var hide = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isCheckedRelevant($el, value)) {\n hide = hide || $el.prop('checked');\n }\n });\n return hide;\n },\n\n noitemselected: function($dependon) {\n var hide = false;\n $dependon.each(function(idx, el) {\n var $el = $(el);\n hide = hide || ($el.prop('selectedIndex') === -1);\n });\n return hide;\n },\n\n eq: function($dependon, value) {\n var hide = false;\n var hiddenVal = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = ($el.val() === value);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // Expect a list of values to match, separated by '|' - all of them must\n // match the values selected.\n var values = value.split('|');\n hide = multiSelectMatches($el, values);\n return;\n }\n // All other element types - just compare the value directly.\n hide = hide || ($el.val() === value);\n });\n return hide;\n },\n\n 'in': function($dependon, value) {\n var hide = false;\n var hiddenVal = false;\n var values = value.split('|');\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = (values.indexOf($el.val()) > -1);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // For multiselect, we check to see if the list of values provided matches the list selected.\n hide = multiSelectMatches($el, values);\n return;\n }\n // All other element types - check to see if the value is in the list.\n hide = hide || (values.indexOf($el.val()) > -1);\n });\n return hide;\n },\n\n defaultCondition: function($dependon, value) { // Not equal.\n var hide = false;\n var hiddenVal = false;\n value = String(value);\n $dependon.each(function(idx, el) {\n var $el = $(el);\n if (isUncheckedRadioButton($el)) {\n // For radio buttons, we're only interested in the one that is checked.\n return;\n }\n if (isCheckboxHiddenElement($el)) {\n // This is the hidden input that is part of the checkbox setting.\n // We will use this value, if the associated checkbox is unchecked.\n hiddenVal = ($el.val() !== value);\n return;\n }\n if (isUncheckedCheckbox($el)) {\n // Checkbox is not checked - hide depends on the 'unchecked' value stored in\n // the associated hidden element, which we have already found, above.\n hide = hide || hiddenVal;\n return;\n }\n if (isMultiSelect($el)) {\n // Expect a list of values to match, separated by '|' - all of them must\n // match the values selected to *not* hide the element.\n var values = value.split('|');\n hide = !multiSelectMatches($el, values);\n return;\n }\n // All other element types - just compare the value directly.\n hide = hide || ($el.val() !== value);\n });\n return hide;\n }\n };\n\n /**\n * Find the element with the given name\n * @param {String} name\n * @returns {*|jQuery|HTMLElement}\n */\n function getElementsByName(name) {\n // For the array elements, we use [name^=\"something[\"] to find the elements that their name begins with 'something['/\n // This is to find both name = 'something[]' and name='something[index]'.\n return $('[name=\"' + name + '\"],[name^=\"' + name + '[\"]');\n }\n\n /**\n * Check to see whether a particular condition is met\n * @param {*|jQuery|HTMLElement} $dependon\n * @param {String} condition\n * @param {mixed} value\n * @returns {Boolean}\n */\n function checkDependency($dependon, condition, value) {\n if (typeof depFns[condition] === \"function\") {\n return depFns[condition]($dependon, value);\n }\n return depFns.defaultCondition($dependon, value);\n }\n\n /**\n * Show / hide the elements that depend on some elements.\n */\n function updateDependencies() {\n // Process all dependency conditions.\n var toHide = {};\n $.each(dependencies, function(dependonname) {\n var dependon = getElementsByName(dependonname);\n $.each(dependencies[dependonname], function(condition, values) {\n $.each(values, function(value, elements) {\n var hide = checkDependency(dependon, condition, value);\n $.each(elements, function(idx, elToHide) {\n if (toHide.hasOwnProperty(elToHide)) {\n toHide[elToHide] = toHide[elToHide] || hide;\n } else {\n toHide[elToHide] = hide;\n }\n });\n });\n });\n });\n\n // Update the hidden status of all relevant elements.\n $.each(toHide, function(elToHide, hide) {\n getElementsByName(elToHide).each(function(idx, el) {\n var $parent = $(el).closest('.form-item');\n if ($parent.length) {\n if (hide) {\n $parent.hide();\n } else {\n $parent.show();\n }\n }\n });\n });\n }\n\n /**\n * Initialise the event handlers.\n */\n function initHandlers() {\n $.each(dependencies, function(depname) {\n var $el = getElementsByName(depname);\n if ($el.length) {\n $el.on('change', updateDependencies);\n }\n });\n updateDependencies();\n }\n\n /**\n * Hide the 'this setting may be hidden' messages.\n */\n function hideDependencyInfo() {\n $('.form-dependenton').hide();\n }\n\n return {\n init: function(opts) {\n dependencies = opts.dependencies;\n initHandlers();\n hideDependencyInfo();\n }\n };\n});"],"names":["define","$","dependencies","isCheckboxHiddenElement","$el","is","siblings","attr","length","isCheckedRelevant","value","isWrongRadioButton","isUncheckedRadioButton","prop","isUncheckedCheckbox","isMultiSelect","multiSelectMatches","values","selected","val","i","hasOwnProperty","indexOf","depFns","notchecked","$dependon","hide","String","each","idx","el","checked","noitemselected","eq","hiddenVal","split","defaultCondition","getElementsByName","name","updateDependencies","toHide","dependonname","dependon","condition","elements","checkDependency","elToHide","$parent","closest","show","init","opts","depname","on"],"mappings":"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"} \ No newline at end of file diff --git a/lib/amd/src/showhidesettings.js b/lib/amd/src/showhidesettings.js index c42faaab354..9ee1f7769dc 100644 --- a/lib/amd/src/showhidesettings.js +++ b/lib/amd/src/showhidesettings.js @@ -78,9 +78,9 @@ define(['jquery'], function($) { */ function multiSelectMatches($el, values) { var selected = $el.val() || []; - if (!values.length) { - // No values - nothing to match against. - return false; + if (values.length === 1 && values[0] === '') { + // Values array contains a single empty entry -> value was empty. + return selected.length === 0; } if (selected.length !== values.length) { // Different number of expected and actual values - cannot possibly be a match. diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php index 10df8eafb58..6c710f732d3 100644 --- a/lib/behat/form_field/behat_form_select.php +++ b/lib/behat/form_field/behat_form_select.php @@ -54,6 +54,13 @@ class behat_form_select extends behat_form_field { // Here we select the option(s). if ($multiple) { + // Is the value empty? + if (empty(trim($value))) { + // Reset selection to nothing. + $this->field->setValue([]); + return; + } + // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash. $options = preg_replace('/\\\,/', ',', preg_split('/(? 0 && selected.length === values.length) { - for (var i in selected) { - v = selected[i]; - if (values.indexOf(v) > -1) { - lock = true; - } else { - lock = false; - return; - } - } - } else { + if (values.length === 1 && values[0] === '') { + // Values array contains a single empty entry -> value was empty. + lock = selected.length === 0; + return; + } + if (selected.length !== values.length) { lock = false; + return; + } + lock = true; + for (const i in selected) { + v = selected[i]; + if (values.indexOf(v) === -1) { + lock = false; + return; + } } } else { lock = lock || this.get('value') == value; @@ -568,18 +572,22 @@ if (typeof M.form.dependencyManager === 'undefined') { selected[selected.length] = this.get('value'); } }); - if (selected.length > 0 && selected.length === values.length) { - for (var i in selected) { - v = selected[i]; - if (values.indexOf(v) > -1) { - lock = true; - } else { - lock = false; - return; - } - } - } else { + if (values.length === 1 && values[0] === '') { + // Values array contains a single empty entry -> value was empty. + lock = selected.length === 0; + return; + } + if (selected.length !== values.length) { lock = false; + return; + } + lock = true; + for (const i in selected) { + v = selected[i]; + if (values.indexOf(v) === -1) { + lock = false; + return; + } } } else { value = this.get('value'); @@ -633,17 +641,21 @@ if (typeof M.form.dependencyManager === 'undefined') { selected[selected.length] = this.get('value'); } }); - if (selected.length > 0 && selected.length === values.length) { - for (var i in selected) { - if (values.indexOf(selected[i]) > -1) { - lock = false; - } else { - lock = true; - return; - } - } - } else { + if (values.length === 1 && values[0] === '') { + // Values array contains a single empty entry -> value was empty. + lock = selected.length !== 0; + return; + } + if (selected.length !== values.length) { lock = true; + return; + } + lock = false; + for (const i in selected) { + if (values.indexOf(selected[i]) === -1) { + lock = true; + return; + } } } else { lock = lock || this.get('value') != value; diff --git a/lib/form/tests/behat/disabledif.feature b/lib/form/tests/behat/disabledif.feature index d4f33ad9607..a0dc2829bed 100644 --- a/lib/form/tests/behat/disabledif.feature +++ b/lib/form/tests/behat/disabledif.feature @@ -27,3 +27,23 @@ Feature: disabledIf functionality in forms And the "class" attribute of "#fitem_id_some_static" "css_element" should not contain "text-muted" And the "#id_some_static_username" "css_element" should be enabled And the "class" attribute of "Check" "button" should not contain "disabled" + + Scenario Outline: Inputs are disabled when disabledIf conditions dependent on a multi-select element are met + Given I am on the "multiselect_hideif_disabledif_form" "core_form > Fixture" page logged in as "admin" + When I set the field "multiselect1" to "" + Then the "#id_disabledIfEq_" "css_element" should be + And the "#id_disabledIfIn_" "css_element" should be + And the "#id_disabledIfNeq_" "css_element" should be + And the "#id_disabledIfEq1" "css_element" should be + And the "#id_disabledIfIn1" "css_element" should be + And the "#id_disabledIfNeq1" "css_element" should be + And the "#id_disabledIfEq12" "css_element" should be + And the "#id_disabledIfIn12" "css_element" should be + And the "#id_disabledIfNeq12" "css_element" should be + + Examples: + | selection | enabledEq_ | enabledIn_ | enabledNeq_ | enabledEq1 | enabledIn1 | enabledNeq1 | enabledEq12 | enabledIn12 | enabledNeq12 | + | | disabled | disabled | enabled | enabled | enabled | disabled | enabled | enabled | disabled | + | Option 1 | enabled | enabled | disabled | disabled | disabled | enabled | enabled | enabled | disabled | + | Option 2 | enabled | enabled | disabled | enabled | enabled | disabled | enabled | enabled | disabled | + | Option 1, Option 2 | enabled | enabled | disabled | enabled | enabled | disabled | disabled | disabled | enabled | diff --git a/lib/form/tests/behat/hideif.feature b/lib/form/tests/behat/hideif.feature index 768c116248b..52f16d7f90a 100644 --- a/lib/form/tests/behat/hideif.feature +++ b/lib/form/tests/behat/hideif.feature @@ -39,3 +39,23 @@ Feature: hideIf functionality in forms Then I should not see "Static with form elements" And I click on "Enable" "radio" And I should see "Static with form elements" + + Scenario Outline: Inputs are hidden when hideIf conditions dependent on a multi-select element are met + Given I am on the "multiselect_hideif_disabledif_form" "core_form > Fixture" page + When I set the field "multiselect1" to "" + Then I see "Hide if selection 'eq' []" + And I see "Hide if selection 'in' []" + And I see "Hide if selection 'neq' []" + And I see "Hide if selection 'eq' ['1']" + And I see "Hide if selection 'in' ['1']" + And I see "Hide if selection 'neq' ['1']" + And I see "Hide if selection 'eq' ['1', '2']" + And I see "Hide if selection 'in' ['1', '2']" + And I see "Hide if selection 'neq' ['1', '2']" + + Examples: + | selection | shouldSeeEq_ | shouldSeeIn_ | shouldSeeNeq_ | shouldSeeEq1 | shouldSeeIn1 | shouldSeeNeq1 | shouldSeeEq12 | shouldSeeIn12 | shouldSeeNeq12 | + | | should not | should not | should | should | should | should not | should | should | should not | + | Option 1 | should | should | should not | should not | should not | should | should | should | should not | + | Option 2 | should | should | should not | should | should | should not | should | should | should not | + | Option 1, Option 2 | should | should | should not | should | should | should not | should not | should not | should | diff --git a/lib/form/tests/fixtures/multiselect_hideif_disabledif_form.php b/lib/form/tests/fixtures/multiselect_hideif_disabledif_form.php new file mode 100644 index 00000000000..154b044f79b --- /dev/null +++ b/lib/form/tests/fixtures/multiselect_hideif_disabledif_form.php @@ -0,0 +1,96 @@ +. + +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/fixtures/multiselect_hideif_disabledif_form.php'); +$PAGE->add_body_class('limitedwidth'); +require_login(); +$PAGE->set_context(core\context\system::instance()); + +/** + * Test class for hiding and disabling elements dependent on a multi-select element. + * + * @package core_form + * @copyright 2024 Lars Bonczek (@innoCampus, TU Berlin) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class test_multiselect_hideif_disabledif_form extends moodleform { + + /** + * Form definition. + */ + public function definition(): void { + $mform = $this->_form; + + $mform->addElement('select', 'multiselect1', 'multiselect1', [ + 1 => 'Option 1', + 2 => 'Option 2', + ], ['multiple' => true]); + + $mform->addElement('checkbox', 'disabledIfEq_', "Disabled if selection 'eq' []"); + $mform->addElement('checkbox', 'disabledIfIn_', "Disabled if selection 'in' []"); + $mform->addElement('checkbox', 'disabledIfNeq_', "Disabled if selection 'neq' []"); + $mform->addElement('checkbox', 'disabledIfEq1', "Disabled if selection 'eq' ['1']"); + $mform->addElement('checkbox', 'disabledIfIn1', "Disabled if selection 'in' ['1']"); + $mform->addElement('checkbox', 'disabledIfNeq1', "Disabled if selection 'neq' ['1']"); + $mform->addElement('checkbox', 'disabledIfEq12', "Disabled if selection 'eq' ['1', '2']"); + $mform->addElement('checkbox', 'disabledIfIn12', "Disabled if selection 'in' ['1', '2']"); + $mform->addElement('checkbox', 'disabledIfNeq12', "Disabled if selection 'neq' ['1', '2']"); + + $mform->disabledIf('disabledIfEq_', 'multiselect1[]', 'eq', []); + $mform->disabledIf('disabledIfIn_', 'multiselect1[]', 'in', []); + $mform->disabledIf('disabledIfNeq_', 'multiselect1[]', 'neq', []); + $mform->disabledIf('disabledIfEq1', 'multiselect1[]', 'eq', ['1']); + $mform->disabledIf('disabledIfIn1', 'multiselect1[]', 'in', ['1']); + $mform->disabledIf('disabledIfNeq1', 'multiselect1[]', 'neq', ['1']); + $mform->disabledIf('disabledIfEq12', 'multiselect1[]', 'eq', ['1', '2']); + $mform->disabledIf('disabledIfIn12', 'multiselect1[]', 'in', ['1', '2']); + $mform->disabledIf('disabledIfNeq12', 'multiselect1[]', 'neq', ['1', '2']); + + $mform->addElement('checkbox', 'hideIfEq_', "Hide if selection 'eq' []"); + $mform->addElement('checkbox', 'hideIfIn_', "Hide if selection 'in' []"); + $mform->addElement('checkbox', 'hideIfNeq_', "Hide if selection 'neq' []"); + $mform->addElement('checkbox', 'hideIfEq1', "Hide if selection 'eq' ['1']"); + $mform->addElement('checkbox', 'hideIfIn1', "Hide if selection 'in' ['1']"); + $mform->addElement('checkbox', 'hideIfNeq1', "Hide if selection 'neq' ['1']"); + $mform->addElement('checkbox', 'hideIfEq12', "Hide if selection 'eq' ['1', '2']"); + $mform->addElement('checkbox', 'hideIfIn12', "Hide if selection 'in' ['1', '2']"); + $mform->addElement('checkbox', 'hideIfNeq12', "Hide if selection 'neq' ['1', '2']"); + + $mform->hideIf('hideIfEq_', 'multiselect1[]', 'eq', []); + $mform->hideIf('hideIfIn_', 'multiselect1[]', 'in', []); + $mform->hideIf('hideIfNeq_', 'multiselect1[]', 'neq', []); + $mform->hideIf('hideIfEq1', 'multiselect1[]', 'eq', ['1']); + $mform->hideIf('hideIfIn1', 'multiselect1[]', 'in', ['1']); + $mform->hideIf('hideIfNeq1', 'multiselect1[]', 'neq', ['1']); + $mform->hideIf('hideIfEq12', 'multiselect1[]', 'eq', ['1', '2']); + $mform->hideIf('hideIfIn12', 'multiselect1[]', 'in', ['1', '2']); + $mform->hideIf('hideIfNeq12', 'multiselect1[]', 'neq', ['1', '2']); + + $this->add_action_buttons(); + } +} + +$form = new test_multiselect_hideif_disabledif_form(); + +echo $OUTPUT->header(); +$form->display(); +echo $OUTPUT->footer(); diff --git a/lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php b/lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php new file mode 100644 index 00000000000..1c9d43d229c --- /dev/null +++ b/lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php @@ -0,0 +1,78 @@ +. + +/** + * Test page for admin setting hide_if functionality dependent on a configmultiselect setting. + * + * @package core + * @copyright 2024 Lars Bonczek (@innoCampus, TU Berlin) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../../../config.php'); + +defined('BEHAT_SITE_RUNNING') || die(); + +global $CFG, $PAGE, $OUTPUT; +require_once($CFG->libdir . '/adminlib.php'); +$PAGE->set_url('/lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php'); +require_login(); +$PAGE->set_context(core\context\system::instance()); + +// Set up dummy admin settings page. +$settings = new \admin_settingpage('hide_if_admin_settingspage', 'hide_if Test'); + +$settings->add(new admin_setting_configmultiselect('multiselect1', 'multiselect1', '', [], [ + 1 => 'Option 1', + 2 => 'Option 2', +])); + +$settings->add(new admin_setting_configcheckbox('hideIfEq_', "Hide if selection 'eq' []", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfIn_', "Hide if selection 'in' []", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfNeq_', "Hide if selection 'neq' []", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfEq1', "Hide if selection 'eq' ['1']", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfIn1', "Hide if selection 'in' ['1']", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfNeq1', "Hide if selection 'neq' ['1']", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfEq12', "Hide if selection 'eq' ['1', '2']", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfIn12', "Hide if selection 'in' ['1', '2']", '', false)); +$settings->add(new admin_setting_configcheckbox('hideIfNeq12', "Hide if selection 'neq' ['1', '2']", '', false)); + +$settings->hide_if('hideIfEq_', 'multiselect1[]', 'eq', ''); +$settings->hide_if('hideIfIn_', 'multiselect1[]', 'in', ''); +$settings->hide_if('hideIfNeq_', 'multiselect1[]', 'neq', ''); +$settings->hide_if('hideIfEq1', 'multiselect1[]', 'eq', '1'); +$settings->hide_if('hideIfIn1', 'multiselect1[]', 'in', '1'); +$settings->hide_if('hideIfNeq1', 'multiselect1[]', 'neq', '1'); +$settings->hide_if('hideIfEq12', 'multiselect1[]', 'eq', '1|2'); +$settings->hide_if('hideIfIn12', 'multiselect1[]', 'in', '1|2'); +$settings->hide_if('hideIfNeq12', 'multiselect1[]', 'neq', '1|2'); + +echo $OUTPUT->header(); + +$context = [ + 'actionurl' => $PAGE->url->out(false), + 'sesskey' => sesskey(), + 'settings' => $settings->output_html(), + 'showsave' => true, +]; +echo $OUTPUT->render_from_template('core_admin/settings', $context); + +$opts = [ + 'dependencies' => $settings->get_dependencies_for_javascript(), +]; +$PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]); + +echo $OUTPUT->footer(); diff --git a/lib/tests/behat/hide_if_admin_settings.feature b/lib/tests/behat/hide_if_admin_settings.feature new file mode 100644 index 00000000000..3c87476fa5f --- /dev/null +++ b/lib/tests/behat/hide_if_admin_settings.feature @@ -0,0 +1,28 @@ +@core @javascript +Feature: hide_if functionality in admin settings + For admin settings using hide_if functionality + As a user + If I trigger the hide_if condition then the admin setting will be hidden + + Background: + Given I log in as "admin" + + Scenario Outline: Admin settings are hidden when hide_if conditions dependent on a configmultiselect setting are met + Given I am on fixture page "/lib/tests/behat/fixtures/multiselect_hide_if_admin_settingspage.php" + When I set the field "s__multiselect1[]" to "" + Then I see "Hide if selection 'eq' []" + And I see "Hide if selection 'in' []" + And I see "Hide if selection 'neq' []" + And I see "Hide if selection 'eq' ['1']" + And I see "Hide if selection 'in' ['1']" + And I see "Hide if selection 'neq' ['1']" + And I see "Hide if selection 'eq' ['1', '2']" + And I see "Hide if selection 'in' ['1', '2']" + And I see "Hide if selection 'neq' ['1', '2']" + + Examples: + | selection | shouldSeeEq_ | shouldSeeIn_ | shouldSeeNeq_ | shouldSeeEq1 | shouldSeeIn1 | shouldSeeNeq1 | shouldSeeEq12 | shouldSeeIn12 | shouldSeeNeq12 | + | | should not | should not | should | should | should | should not | should | should | should not | + | Option 1 | should | should | should not | should not | should not | should | should | should | should not | + | Option 2 | should | should | should not | should | should | should not | should | should | should not | + | Option 1, Option 2 | should | should | should not | should | should | should not | should not | should not | should |