grade/report/user/amd/src/gradecategorytoggle.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. * Javascript module for toggling the visibility of the grade categories in the user report.
  17. *
  18. * @module gradereport_user/gradecategorytoggle
  19. * @copyright 2022 Mihail Geshoski <mihail@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. const SELECTORS = {
  23. CATEGORY_TOGGLE: '.toggle-category',
  24. USER_REPORT_TABLE: '.user-grade'
  25. };
  26. /**
  27. * Register related event listeners.
  28. *
  29. * @method registerListenerEvents
  30. * @param {string} userReportId The ID of the user report container element.
  31. */
  32. const registerListenerEvents = (userReportId) => {
  33. const reportContainer = document.querySelector('#' + userReportId);
  34. const userReport = reportContainer.querySelector(SELECTORS.USER_REPORT_TABLE);
  35. userReport.addEventListener('click', e => {
  36. const toggle = e.target.closest(SELECTORS.CATEGORY_TOGGLE);
  37. if (toggle) {
  38. e.preventDefault();
  39. toggleCategory(toggle);
  40. }
  41. });
  42. };
  43. /**
  44. * Method that handles the category toggle action.
  45. *
  46. * @method toggleCategory
  47. * @param {object} toggleElement The category toggle node that was clicked.
  48. */
  49. const toggleCategory = (toggleElement) => {
  50. const target = toggleElement.dataset.target;
  51. const categoryId = toggleElement.dataset.categoryid;
  52. // Whether the toggle action is collapsing the category or not.
  53. const isCollapsing = toggleElement.getAttribute('aria-expanded') === "true";
  54. const userReport = toggleElement.closest(SELECTORS.USER_REPORT_TABLE);
  55. // Find all targeted 'children' rows of the toggled category.
  56. const targetRows = userReport.querySelectorAll(target);
  57. if (isCollapsing) {
  58. toggleElement.setAttribute('aria-expanded', 'false');
  59. // Update the 'data-target' of the toggle category node to make sure that when we perform another toggle action
  60. // to expand this category we only target rows which have been hidden by this category toggle action.
  61. toggleElement.dataset.target = `[data-hidden-by='${categoryId}']`;
  62. } else {
  63. toggleElement.setAttribute('aria-expanded', 'true');
  64. // Update the 'data-target' of the toggle category node to make sure that when we perform another toggle action
  65. // to collapse this category we only target rows which are children of this category and are not currently hidden.
  66. toggleElement.dataset.target = `.cat_${categoryId}[data-hidden='false']`;
  67. }
  68. // Loop through all targeted children row elements and update the required data attributes to either hide or show
  69. // them depending on the toggle action (collapsing or expanding).
  70. targetRows.forEach((row) => {
  71. if (isCollapsing) {
  72. row.dataset.hidden = 'true';
  73. row.dataset.hiddenBy = categoryId;
  74. } else {
  75. row.dataset.hidden = 'false';
  76. row.dataset.hiddenBy = '';
  77. }
  78. });
  79. // Since the user report is presented in an HTML table, rowspans are used under each category to create a visual
  80. // hierarchy between categories and grading items. When expanding or collapsing a category we need to also update
  81. // (subtract or add) the rowspan values associated to each parent category row to preserve the correct visual
  82. // hierarchy in the table.
  83. updateParentCategoryRowspans(toggleElement, targetRows.length);
  84. };
  85. /**
  86. * Method that updates the rowspan value of all 'parent' category rows of a given category node.
  87. *
  88. * @method updateParentCategoryRowspans
  89. * @param {object} toggleElement The category toggle node that was clicked.
  90. * @param {int} num The number we want to add or subtract from the rowspan value of the 'parent' category row elements.
  91. */
  92. const updateParentCategoryRowspans = (toggleElement, num) => {
  93. const userReport = toggleElement.closest(SELECTORS.USER_REPORT_TABLE);
  94. // Get the row element which contains the category toggle node.
  95. const rowElement = toggleElement.closest('tr');
  96. // Loop through the class list of the toggle category row element.
  97. // The list contains classes which identify all parent categories of the toggled category.
  98. rowElement.classList.forEach((className) => {
  99. // Find the toggle node of the 'parent' category that is identified by the given class name.
  100. const parentCategoryToggleElement = userReport.querySelector(`[data-target=".${className}[data-hidden='false']"`);
  101. if (parentCategoryToggleElement) {
  102. // Get the row element which contains the parent category toggle node.
  103. const categoryRowElement = parentCategoryToggleElement.closest('tr');
  104. // Find the rowspan element associated to this parent category.
  105. const categoryRowSpanElement = categoryRowElement.nextElementSibling.querySelector('[rowspan]');
  106. // Depending on whether the toggle action has expanded or collapsed the category, either add or
  107. // subtract from the 'parent' category rowspan.
  108. if (toggleElement.getAttribute('aria-expanded') === "true") {
  109. categoryRowSpanElement.rowSpan = categoryRowSpanElement.rowSpan + num;
  110. } else { // The category has been collapsed.
  111. categoryRowSpanElement.rowSpan = categoryRowSpanElement.rowSpan - num;
  112. }
  113. }
  114. });
  115. };
  116. /**
  117. * Init method.
  118. *
  119. * @param {string} userReportId The ID of the user report container element.
  120. */
  121. export const init = (userReportId) => {
  122. registerListenerEvents(userReportId);
  123. };