admin/tool/capability/amd/src/search.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. * Add search filtering of capabilities
  17. *
  18. * @module tool_capability/search
  19. * @copyright 2023 Paul Holden <paulh@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import Pending from 'core/pending';
  23. import {debounce} from 'core/utils';
  24. const Selectors = {
  25. capabilityOverviewForm: '#capability-overview-form',
  26. capabilitySelect: '[data-search="capability"]',
  27. capabilitySearch: '[data-action="search"]',
  28. };
  29. const debounceTimer = 250;
  30. /**
  31. * Initialize module
  32. */
  33. export const init = () => {
  34. const capabilityOverviewForm = document.querySelector(Selectors.capabilityOverviewForm);
  35. if (!capabilityOverviewForm) {
  36. return;
  37. }
  38. const capabilitySelect = capabilityOverviewForm.querySelector(Selectors.capabilitySelect);
  39. const capabilitySearch = capabilityOverviewForm.querySelector(Selectors.capabilitySearch);
  40. const capabilitySelectFilter = searchTerm => {
  41. const pendingPromise = new Pending('tool_capability/search:filter');
  42. // Remove existing options, remembering which were previously selected.
  43. let capabilitySelected = [];
  44. capabilitySelect.querySelectorAll('option').forEach(option => {
  45. if (option.selected) {
  46. capabilitySelected.push(option.value);
  47. }
  48. option.remove();
  49. });
  50. // Filter for matching capabilities.
  51. const availableCapabilities = JSON.parse(capabilitySelect.dataset.availableCapabilities);
  52. const filteredCapabilities = Object.keys(availableCapabilities).reduce((matches, capability) => {
  53. if (availableCapabilities[capability].toLowerCase().includes(searchTerm)) {
  54. matches[capability] = availableCapabilities[capability];
  55. }
  56. return matches;
  57. }, []);
  58. // Re-create filtered options.
  59. Object.entries(filteredCapabilities).forEach(([capability, capabilityText]) => {
  60. const option = document.createElement('option');
  61. option.value = capability;
  62. option.innerText = capabilityText;
  63. option.selected = capabilitySelected.indexOf(capability) > -1;
  64. capabilitySelect.append(option);
  65. });
  66. pendingPromise.resolve();
  67. };
  68. // Cache initial capability options.
  69. const availableCapabilities = {};
  70. capabilitySelect.querySelectorAll('option').forEach(option => {
  71. availableCapabilities[option.value] = option.text;
  72. });
  73. capabilitySelect.dataset.availableCapabilities = JSON.stringify(availableCapabilities);
  74. // Debounce the event listener on the search element to allow user to finish typing.
  75. const capabilitySearchDebounce = debounce(capabilitySelectFilter, debounceTimer);
  76. capabilitySearch.addEventListener('keyup', event => {
  77. const pendingPromise = new Pending('tool_capability/search:keyup');
  78. capabilitySearchDebounce(event.target.value.toLowerCase());
  79. setTimeout(() => {
  80. pendingPromise.resolve();
  81. }, debounceTimer);
  82. });
  83. // Ensure filter is applied on form load.
  84. if (capabilitySearch.value !== '') {
  85. capabilitySelectFilter(capabilitySearch.value.toLowerCase());
  86. }
  87. };