admin/tool/usertours/amd/src/tour_filters.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * User tours filters.
  17. *
  18. * @module tool_usertours/tour_filters
  19. * @copyright 2025 The Open University
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. const ANY_VALUE = "__ANYVALUE__";
  23. export const init = () => {
  24. // Initialize the category filter
  25. initConfigurationCategoryFilter();
  26. };
  27. /**
  28. * Initialize the category filter for the configuration page.
  29. */
  30. const initConfigurationCategoryFilter = () => {
  31. const categorySelect = document.querySelector("[name='filter_category[]']");
  32. const excludeSelect = document.querySelector("[name='filter_exclude_category[]']");
  33. const excludeCategoriesContainer = document.getElementById('fitem_id_filter_exclude_category');
  34. if (categorySelect && excludeSelect) {
  35. // Add event listeners to update the exclude categories when the include categories change.
  36. categorySelect.addEventListener("change", () => {
  37. updateExcludeCategories(categorySelect, excludeSelect, excludeCategoriesContainer);
  38. });
  39. // Initialize the exclude categories based on the selected include categories.
  40. updateExcludeCategories(categorySelect, excludeSelect, excludeCategoriesContainer);
  41. }
  42. };
  43. /**
  44. * Adjust the height of a select element based on the number of options.
  45. *
  46. * @param {HTMLSelectElement} select
  47. */
  48. const adjustHeight = (select) => {
  49. select.size = Math.min(select.options.length || 1, 10);
  50. };
  51. /**
  52. * Update the exclude categories based on the selected include categories.
  53. *
  54. * @param {HTMLSelectElement} categorySelect
  55. * @param {HTMLSelectElement} excludeSelect
  56. * @param {HTMLElement} excludeCategoriesContainer
  57. */
  58. const updateExcludeCategories = (categorySelect, excludeSelect, excludeCategoriesContainer) => {
  59. // Get the selected categories and update the 'Any' option.
  60. const selectedCategories = new Set(Array.from(categorySelect.selectedOptions).map(option => option.value));
  61. // Get the selected exclude categories and create a map of options.
  62. const excludeSelected = new Set(Array.from(excludeSelect.selectedOptions).map(option => option.value));
  63. const excludeOptions = new Map();
  64. // Flag to check if 'Any' value is selected.
  65. const anySelected = selectedCategories.has(ANY_VALUE);
  66. Array.from(categorySelect.options).forEach(option => {
  67. const isNotAny = option.value !== ANY_VALUE;
  68. // If 'Any' is selected, include all options in excludeOptions.
  69. if (anySelected && isNotAny) {
  70. excludeOptions.set(option.value, option.text);
  71. } else if (isNotAny) {
  72. // Otherwise, check if the option is a child of any selected category.
  73. for (const selected of selectedCategories) {
  74. const selectedOption = categorySelect.querySelector(`option[value="${selected}"]`);
  75. if (option.text.startsWith(`${selectedOption.text} / `)) {
  76. excludeOptions.set(option.value, option.text);
  77. break;
  78. }
  79. }
  80. }
  81. });
  82. if (excludeOptions.size) {
  83. // Update the exclude categories select element.
  84. excludeSelect.innerHTML = '';
  85. Array.from(excludeOptions)
  86. .sort(([, a], [, b]) => a.localeCompare(b))
  87. .forEach(([key, value]) => {
  88. const option = document.createElement("option");
  89. option.value = key;
  90. option.text = value;
  91. if (excludeSelected.has(key)) {
  92. option.selected = true;
  93. }
  94. excludeSelect.appendChild(option);
  95. });
  96. // Adjust the height of the select elements.
  97. adjustHeight(excludeSelect);
  98. // Show the exclude categories container if it was hidden.
  99. if (excludeCategoriesContainer.classList.contains('d-none')) {
  100. excludeCategoriesContainer.classList.remove('d-none');
  101. }
  102. } else {
  103. // Hide the exclude categories container when no child categories exist.
  104. if (!excludeCategoriesContainer.classList.contains('d-none')) {
  105. excludeCategoriesContainer.classList.add('d-none');
  106. }
  107. // Clear selections to prevent submitting excluded categories when container is hidden.
  108. excludeSelect.innerHTML = '';
  109. }
  110. };