contentbank/amd/src/sort.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. * Content bank UI actions.
  17. *
  18. * @module core_contentbank/sort
  19. * @copyright 2020 Bas Brands <bas@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import selectors from './selectors';
  23. import {getString} from 'core/str';
  24. import Prefetch from 'core/prefetch';
  25. import Notification from 'core/notification';
  26. import {setUserPreference} from 'core_user/repository';
  27. /**
  28. * Set up the contentbank views.
  29. *
  30. * @method init
  31. */
  32. export const init = () => {
  33. const contentBank = document.querySelector(selectors.regions.contentbank);
  34. Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);
  35. Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);
  36. registerListenerEvents(contentBank);
  37. };
  38. /**
  39. * Register contentbank related event listeners.
  40. *
  41. * @method registerListenerEvents
  42. * @param {HTMLElement} contentBank The DOM node of the content bank
  43. */
  44. const registerListenerEvents = (contentBank) => {
  45. contentBank.addEventListener('click', e => {
  46. const viewList = contentBank.querySelector(selectors.actions.viewlist);
  47. const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);
  48. const fileArea = contentBank.querySelector(selectors.regions.filearea);
  49. const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);
  50. // View as Grid button.
  51. if (e.target.closest(selectors.actions.viewgrid)) {
  52. contentBank.classList.remove('view-list');
  53. contentBank.classList.add('view-grid');
  54. if (fileArea && shownItems) {
  55. fileArea.setAttribute('role', 'list');
  56. shownItems.forEach(listItem => {
  57. listItem.setAttribute('role', 'listitem');
  58. listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));
  59. });
  60. const heading = fileArea.querySelector(selectors.elements.heading);
  61. if (heading) {
  62. heading.removeAttribute('role');
  63. heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));
  64. }
  65. }
  66. viewGrid.classList.add('active');
  67. viewList.classList.remove('active');
  68. setViewListPreference(false);
  69. return;
  70. }
  71. // View as List button.
  72. if (e.target.closest(selectors.actions.viewlist)) {
  73. contentBank.classList.remove('view-grid');
  74. contentBank.classList.add('view-list');
  75. if (fileArea && shownItems) {
  76. fileArea.setAttribute('role', 'table');
  77. shownItems.forEach(listItem => {
  78. listItem.setAttribute('role', 'row');
  79. listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell'));
  80. });
  81. const heading = fileArea.querySelector(selectors.elements.heading);
  82. if (heading) {
  83. heading.setAttribute('role', 'row');
  84. heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader'));
  85. }
  86. }
  87. viewList.classList.add('active');
  88. viewGrid.classList.remove('active');
  89. setViewListPreference(true);
  90. return;
  91. }
  92. if (fileArea && shownItems) {
  93. // Sort by file name alphabetical
  94. const sortByName = e.target.closest(selectors.actions.sortname);
  95. if (sortByName) {
  96. const ascending = updateSortButtons(contentBank, sortByName);
  97. updateSortOrder(fileArea, shownItems, 'data-file', ascending);
  98. return;
  99. }
  100. // Sort by uses.
  101. const sortByUses = e.target.closest(selectors.actions.sortuses);
  102. if (sortByUses) {
  103. const ascending = updateSortButtons(contentBank, sortByUses);
  104. updateSortOrder(fileArea, shownItems, 'data-uses', ascending);
  105. return;
  106. }
  107. // Sort by date.
  108. const sortByDate = e.target.closest(selectors.actions.sortdate);
  109. if (sortByDate) {
  110. const ascending = updateSortButtons(contentBank, sortByDate);
  111. updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);
  112. return;
  113. }
  114. // Sort by size.
  115. const sortBySize = e.target.closest(selectors.actions.sortsize);
  116. if (sortBySize) {
  117. const ascending = updateSortButtons(contentBank, sortBySize);
  118. updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);
  119. return;
  120. }
  121. // Sort by type.
  122. const sortByType = e.target.closest(selectors.actions.sorttype);
  123. if (sortByType) {
  124. const ascending = updateSortButtons(contentBank, sortByType);
  125. updateSortOrder(fileArea, shownItems, 'data-type', ascending);
  126. return;
  127. }
  128. // Sort by author.
  129. const sortByAuthor = e.target.closest(selectors.actions.sortauthor);
  130. if (sortByAuthor) {
  131. const ascending = updateSortButtons(contentBank, sortByAuthor);
  132. updateSortOrder(fileArea, shownItems, 'data-author', ascending);
  133. }
  134. return;
  135. }
  136. });
  137. };
  138. /**
  139. * Set the contentbank user preference in list view
  140. *
  141. * @param {Bool} viewList view ContentBank as list.
  142. * @return {Promise} Repository promise.
  143. */
  144. const setViewListPreference = function(viewList) {
  145. // If the given status is not hidden, the preference has to be deleted with a null value.
  146. if (viewList === false) {
  147. viewList = null;
  148. }
  149. return setUserPreference('core_contentbank_view_list', viewList)
  150. .catch(Notification.exception);
  151. };
  152. /**
  153. * Update the sort button view.
  154. *
  155. * @method updateSortButtons
  156. * @param {HTMLElement} contentBank The DOM node of the contentbank button
  157. * @param {HTMLElement} sortButton The DOM node of the sort button
  158. * @return {Bool} sort ascending
  159. */
  160. const updateSortButtons = (contentBank, sortButton) => {
  161. const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);
  162. sortButtons.forEach((button) => {
  163. if (button !== sortButton) {
  164. button.classList.remove('dir-asc');
  165. button.classList.remove('dir-desc');
  166. button.classList.add('dir-none');
  167. button.closest(selectors.elements.cell).setAttribute('aria-sort', 'none');
  168. updateButtonTitle(button, false);
  169. }
  170. });
  171. let ascending = true;
  172. if (sortButton.classList.contains('dir-none')) {
  173. sortButton.classList.remove('dir-none');
  174. sortButton.classList.add('dir-asc');
  175. sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');
  176. } else if (sortButton.classList.contains('dir-asc')) {
  177. sortButton.classList.remove('dir-asc');
  178. sortButton.classList.add('dir-desc');
  179. sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'descending');
  180. ascending = false;
  181. } else if (sortButton.classList.contains('dir-desc')) {
  182. sortButton.classList.remove('dir-desc');
  183. sortButton.classList.add('dir-asc');
  184. sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');
  185. }
  186. updateButtonTitle(sortButton, ascending);
  187. return ascending;
  188. };
  189. /**
  190. * Update the button title.
  191. *
  192. * @method updateButtonTitle
  193. * @param {HTMLElement} button Button to update
  194. * @param {Bool} ascending Sort direction
  195. * @return {Promise} string promise
  196. */
  197. const updateButtonTitle = (button, ascending) => {
  198. const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');
  199. return getString(button.dataset.string, 'contentbank')
  200. .then(columnName => {
  201. return getString(sortString, 'core', columnName);
  202. })
  203. .then(sortByString => {
  204. button.setAttribute('title', sortByString);
  205. return sortByString;
  206. })
  207. .catch();
  208. };
  209. /**
  210. * Update the sort order of the itemlist and update the DOM
  211. *
  212. * @method updateSortOrder
  213. * @param {HTMLElement} fileArea the Dom container for the itemlist
  214. * @param {Array} itemList Nodelist of Dom elements
  215. * @param {String} attribute the attribut to sort on
  216. * @param {Bool} ascending Sort Ascending
  217. */
  218. const updateSortOrder = (fileArea, itemList, attribute, ascending) => {
  219. const sortList = [].slice.call(itemList).sort(function(a, b) {
  220. let aa = a.getAttribute(attribute);
  221. let bb = b.getAttribute(attribute);
  222. if (!isNaN(aa)) {
  223. aa = parseInt(aa);
  224. bb = parseInt(bb);
  225. }
  226. if (ascending) {
  227. return aa > bb ? 1 : -1;
  228. } else {
  229. return aa < bb ? 1 : -1;
  230. }
  231. });
  232. sortList.forEach(listItem => fileArea.appendChild(listItem));
  233. };