course/format/amd/src/local/content/section/header.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. * Course section header component.
  17. *
  18. * This component is used to control specific course section interactions like drag and drop.
  19. *
  20. * @module core_courseformat/local/content/section/header
  21. * @class core_courseformat/local/content/section/header
  22. * @copyright 2021 Ferran Recio <ferran@moodle.com>
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. import DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';
  26. export default class extends DndSectionItem {
  27. /**
  28. * Constructor hook.
  29. *
  30. * @param {Object} descriptor
  31. */
  32. create(descriptor) {
  33. // Optional component name for debugging.
  34. this.name = 'content_section_header';
  35. // Default query selectors.
  36. this.selectors = {
  37. ACTIONSMENU: `.section_action_menu`,
  38. BULKSELECT: `[data-for='sectionBulkSelect']`,
  39. BULKCHECKBOX: `[data-bulkcheckbox]`,
  40. CHEVRON: `[data-for='sectiontoggler']`,
  41. };
  42. this.classes = {
  43. HIDE: 'd-none',
  44. SELECTED: 'selected',
  45. };
  46. // Get main info from the descriptor.
  47. this.id = descriptor.id;
  48. this.section = descriptor.section;
  49. this.course = descriptor.course;
  50. this.fullregion = descriptor.fullregion;
  51. }
  52. /**
  53. * Initial state ready method.
  54. *
  55. * @param {Object} state the initial state
  56. */
  57. stateReady(state) {
  58. this.configDragDrop(this.id, state, this.fullregion);
  59. this._refreshBulk({state});
  60. }
  61. /**
  62. * Component watchers.
  63. *
  64. * @returns {Array} of watchers
  65. */
  66. getWatchers() {
  67. return [
  68. {watch: `bulk:updated`, handler: this._refreshBulk},
  69. {watch: `section[${this.id}].title:updated`, handler: this._refreshSectionTitle},
  70. ];
  71. }
  72. /**
  73. * Update the section when the section name changes.
  74. *
  75. * The section header have several HTML that uses the section name
  76. * for accessibility and behat tests. This method updates them all.
  77. *
  78. * @param {object} param
  79. * @param {Object} param.element the section info
  80. */
  81. _refreshSectionTitle(param) {
  82. const element = param.element;
  83. this.getElement(this.selectors.CHEVRON)?.setAttribute("aria-label", element.title);
  84. this._refreshSectionBulkSelector(param);
  85. }
  86. /**
  87. * Update the bulk checkbox when the section name changes.
  88. *
  89. * @param {object} param
  90. * @param {Object} param.element the section info
  91. */
  92. async _refreshSectionBulkSelector({element}) {
  93. const checkbox = this.getElement(this.selectors.BULKCHECKBOX);
  94. if (!checkbox) {
  95. return;
  96. }
  97. const newLabel = await this.reactive.getFormatString('selectsection', element.title);
  98. checkbox.title = newLabel;
  99. const label = this.getElement(`label[for='${checkbox.id}']`);
  100. if (label) {
  101. label.innerText = newLabel;
  102. }
  103. }
  104. /**
  105. * Update a bulk options.
  106. *
  107. * @param {object} param
  108. * @param {Object} param.state the state data
  109. */
  110. _refreshBulk({state}) {
  111. const bulk = state.bulk;
  112. if (!this._isSectionBulkEditable()) {
  113. return;
  114. }
  115. // For now, dragging elements in bulk is not possible.
  116. this.setDraggable(!bulk.enabled);
  117. this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);
  118. const disabled = !this._isSectionBulkEnabled(bulk);
  119. const selected = this._isSelected(bulk);
  120. this.element.classList.toggle(this.classes.SELECTED, selected);
  121. this._setCheckboxValue(selected, disabled);
  122. }
  123. /**
  124. * Modify the checkbox element.
  125. * @param {Boolean} checked the new checked value
  126. * @param {Boolean} disabled the new disabled value
  127. */
  128. _setCheckboxValue(checked, disabled) {
  129. const checkbox = this.getElement(this.selectors.BULKCHECKBOX);
  130. if (!checkbox) {
  131. return;
  132. }
  133. checkbox.checked = checked;
  134. checkbox.disabled = disabled;
  135. // Is selectable is used to easily scan the page for bulk checkboxes.
  136. if (disabled) {
  137. checkbox.removeAttribute('data-is-selectable');
  138. } else {
  139. checkbox.dataset.isSelectable = 1;
  140. }
  141. }
  142. /**
  143. * Check if cm bulk selection is available.
  144. * @param {Object} bulk the current state bulk attribute
  145. * @returns {Boolean}
  146. */
  147. _isSectionBulkEnabled(bulk) {
  148. if (!bulk.enabled) {
  149. return false;
  150. }
  151. return (bulk.selectedType === '' || bulk.selectedType === 'section');
  152. }
  153. /**
  154. * Check if the section is bulk editable.
  155. * @return {Boolean}
  156. */
  157. _isSectionBulkEditable() {
  158. const section = this.reactive.get('section', this.id);
  159. return section?.bulkeditable ?? false;
  160. }
  161. /**
  162. * Check if the cm id is part of the current bulk selection.
  163. * @param {Object} bulk the current state bulk attribute
  164. * @returns {Boolean}
  165. */
  166. _isSelected(bulk) {
  167. if (bulk.selectedType !== 'section') {
  168. return false;
  169. }
  170. return bulk.selection.includes(this.id);
  171. }
  172. }