Merge branch 'MDL-81713-main' of https://github.com/junpataleta/moodle
[moodle.git] / lib / amd / src / showhidesettings.js
blobc42faaab354085f277438ed72c5853a9f9e1cc1f
1 /**
2  * Show/hide admin settings based on other settings selected
3  *
4  * @copyright 2018 Davo Smith, Synergy Learning
5  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6  */
7 define(['jquery'], function($) {
8     var dependencies;
10     // -------------------------------------------------
11     // Support functions, used by dependency functions.
12     // -------------------------------------------------
14     /**
15      * Check to see if the given element is the hidden element that makes sure checkbox
16      * elements always submit a value.
17      * @param {jQuery} $el
18      * @returns {boolean}
19      */
20     function isCheckboxHiddenElement($el) {
21         return ($el.is('input[type=hidden]') && $el.siblings('input[type=checkbox][name="' + $el.attr('name') + '"]').length);
22     }
24     /**
25      * Check to see if this is a radio button with the wrong value (i.e. a radio button from
26      * the group we are interested in, but not the specific one we wanted).
27      * @param {jQuery} $el
28      * @param {string} value
29      * @returns {boolean}
30      */
31     function isWrongRadioButton($el, value) {
32         return ($el.is('input[type=radio]') && $el.attr('value') !== value);
33     }
35     /**
36      * Is this element relevant when we're looking for checked / not checked status?
37      * @param {jQuery} $el
38      * @param {string} value
39      * @returns {boolean}
40      */
41     function isCheckedRelevant($el, value) {
42         return (!isCheckboxHiddenElement($el) && !isWrongRadioButton($el, value));
43     }
45     /**
46      * Is this an unchecked radio button? (If it is, we want to skip it, as
47      * we're only interested in the value of the radio button that is checked)
48      * @param {jQuery} $el
49      * @returns {boolean}
50      */
51     function isUncheckedRadioButton($el) {
52         return ($el.is('input[type=radio]') && !$el.prop('checked'));
53     }
55     /**
56      * Is this an unchecked checkbox?
57      * @param {jQuery} $el
58      * @returns {boolean}
59      */
60     function isUncheckedCheckbox($el) {
61         return ($el.is('input[type=checkbox]') && !$el.prop('checked'));
62     }
64     /**
65      * Is this a multi-select select element?
66      * @param {jQuery} $el
67      * @returns {boolean}
68      */
69     function isMultiSelect($el) {
70         return ($el.is('select') && $el.prop('multiple'));
71     }
73     /**
74      * Does the multi-select exactly match the list of values provided?
75      * @param {jQuery} $el
76      * @param {array} values
77      * @returns {boolean}
78      */
79     function multiSelectMatches($el, values) {
80         var selected = $el.val() || [];
81         if (!values.length) {
82             // No values - nothing to match against.
83             return false;
84         }
85         if (selected.length !== values.length) {
86             // Different number of expected and actual values - cannot possibly be a match.
87             return false;
88         }
89         for (var i in selected) {
90             if (selected.hasOwnProperty(i)) {
91                 if (values.indexOf(selected[i]) === -1) {
92                     return false; // Found a non-matching value - give up immediately.
93                 }
94             }
95         }
96         // Didn't find a non-matching value, so we have a match.
97         return true;
98     }
100     // -------------------------------
101     // Specific dependency functions.
102     // -------------------------------
104     var depFns = {
105         notchecked: function($dependon, value) {
106             var hide = false;
107             value = String(value);
108             $dependon.each(function(idx, el) {
109                 var $el = $(el);
110                 if (isCheckedRelevant($el, value)) {
111                     hide = hide || !$el.prop('checked');
112                 }
113             });
114             return hide;
115         },
117         checked: function($dependon, value) {
118             var hide = false;
119             value = String(value);
120             $dependon.each(function(idx, el) {
121                 var $el = $(el);
122                 if (isCheckedRelevant($el, value)) {
123                     hide = hide || $el.prop('checked');
124                 }
125             });
126             return hide;
127         },
129         noitemselected: function($dependon) {
130             var hide = false;
131             $dependon.each(function(idx, el) {
132                 var $el = $(el);
133                 hide = hide || ($el.prop('selectedIndex') === -1);
134             });
135             return hide;
136         },
138         eq: function($dependon, value) {
139             var hide = false;
140             var hiddenVal = false;
141             value = String(value);
142             $dependon.each(function(idx, el) {
143                 var $el = $(el);
144                 if (isUncheckedRadioButton($el)) {
145                     // For radio buttons, we're only interested in the one that is checked.
146                     return;
147                 }
148                 if (isCheckboxHiddenElement($el)) {
149                     // This is the hidden input that is part of the checkbox setting.
150                     // We will use this value, if the associated checkbox is unchecked.
151                     hiddenVal = ($el.val() === value);
152                     return;
153                 }
154                 if (isUncheckedCheckbox($el)) {
155                     // Checkbox is not checked - hide depends on the 'unchecked' value stored in
156                     // the associated hidden element, which we have already found, above.
157                     hide = hide || hiddenVal;
158                     return;
159                 }
160                 if (isMultiSelect($el)) {
161                     // Expect a list of values to match, separated by '|' - all of them must
162                     // match the values selected.
163                     var values = value.split('|');
164                     hide = multiSelectMatches($el, values);
165                     return;
166                 }
167                 // All other element types - just compare the value directly.
168                 hide = hide || ($el.val() === value);
169             });
170             return hide;
171         },
173         'in': function($dependon, value) {
174             var hide = false;
175             var hiddenVal = false;
176             var values = value.split('|');
177             $dependon.each(function(idx, el) {
178                 var $el = $(el);
179                 if (isUncheckedRadioButton($el)) {
180                     // For radio buttons, we're only interested in the one that is checked.
181                     return;
182                 }
183                 if (isCheckboxHiddenElement($el)) {
184                     // This is the hidden input that is part of the checkbox setting.
185                     // We will use this value, if the associated checkbox is unchecked.
186                     hiddenVal = (values.indexOf($el.val()) > -1);
187                     return;
188                 }
189                 if (isUncheckedCheckbox($el)) {
190                     // Checkbox is not checked - hide depends on the 'unchecked' value stored in
191                     // the associated hidden element, which we have already found, above.
192                     hide = hide || hiddenVal;
193                     return;
194                 }
195                 if (isMultiSelect($el)) {
196                     // For multiselect, we check to see if the list of values provided matches the list selected.
197                     hide = multiSelectMatches($el, values);
198                     return;
199                 }
200                 // All other element types - check to see if the value is in the list.
201                 hide = hide || (values.indexOf($el.val()) > -1);
202             });
203             return hide;
204         },
206         defaultCondition: function($dependon, value) { // Not equal.
207             var hide = false;
208             var hiddenVal = false;
209             value = String(value);
210             $dependon.each(function(idx, el) {
211                 var $el = $(el);
212                 if (isUncheckedRadioButton($el)) {
213                     // For radio buttons, we're only interested in the one that is checked.
214                     return;
215                 }
216                 if (isCheckboxHiddenElement($el)) {
217                     // This is the hidden input that is part of the checkbox setting.
218                     // We will use this value, if the associated checkbox is unchecked.
219                     hiddenVal = ($el.val() !== value);
220                     return;
221                 }
222                 if (isUncheckedCheckbox($el)) {
223                     // Checkbox is not checked - hide depends on the 'unchecked' value stored in
224                     // the associated hidden element, which we have already found, above.
225                     hide = hide || hiddenVal;
226                     return;
227                 }
228                 if (isMultiSelect($el)) {
229                     // Expect a list of values to match, separated by '|' - all of them must
230                     // match the values selected to *not* hide the element.
231                     var values = value.split('|');
232                     hide = !multiSelectMatches($el, values);
233                     return;
234                 }
235                 // All other element types - just compare the value directly.
236                 hide = hide || ($el.val() !== value);
237             });
238             return hide;
239         }
240     };
242     /**
243      * Find the element with the given name
244      * @param {String} name
245      * @returns {*|jQuery|HTMLElement}
246      */
247     function getElementsByName(name) {
248         // For the array elements, we use [name^="something["] to find the elements that their name begins with 'something['/
249         // This is to find both name = 'something[]' and name='something[index]'.
250         return $('[name="' + name + '"],[name^="' + name + '["]');
251     }
253     /**
254      * Check to see whether a particular condition is met
255      * @param {*|jQuery|HTMLElement} $dependon
256      * @param {String} condition
257      * @param {mixed} value
258      * @returns {Boolean}
259      */
260     function checkDependency($dependon, condition, value) {
261         if (typeof depFns[condition] === "function") {
262             return depFns[condition]($dependon, value);
263         }
264         return depFns.defaultCondition($dependon, value);
265     }
267     /**
268      * Show / hide the elements that depend on some elements.
269      */
270     function updateDependencies() {
271         // Process all dependency conditions.
272         var toHide = {};
273         $.each(dependencies, function(dependonname) {
274             var dependon = getElementsByName(dependonname);
275             $.each(dependencies[dependonname], function(condition, values) {
276                 $.each(values, function(value, elements) {
277                     var hide = checkDependency(dependon, condition, value);
278                     $.each(elements, function(idx, elToHide) {
279                         if (toHide.hasOwnProperty(elToHide)) {
280                             toHide[elToHide] = toHide[elToHide] || hide;
281                         } else {
282                             toHide[elToHide] = hide;
283                         }
284                     });
285                 });
286             });
287         });
289         // Update the hidden status of all relevant elements.
290         $.each(toHide, function(elToHide, hide) {
291             getElementsByName(elToHide).each(function(idx, el) {
292                 var $parent = $(el).closest('.form-item');
293                 if ($parent.length) {
294                     if (hide) {
295                         $parent.hide();
296                     } else {
297                         $parent.show();
298                     }
299                 }
300             });
301         });
302     }
304     /**
305      * Initialise the event handlers.
306      */
307     function initHandlers() {
308         $.each(dependencies, function(depname) {
309             var $el = getElementsByName(depname);
310             if ($el.length) {
311                 $el.on('change', updateDependencies);
312             }
313         });
314         updateDependencies();
315     }
317     /**
318      * Hide the 'this setting may be hidden' messages.
319      */
320     function hideDependencyInfo() {
321         $('.form-dependenton').hide();
322     }
324     return {
325         init: function(opts) {
326             dependencies = opts.dependencies;
327             initHandlers();
328             hideDependencyInfo();
329         }
330     };