// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Content bank UI actions.
*
* @module core_contentbank/sort
* @copyright 2020 Bas Brands <bas@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import selectors from './selectors';
import {getString} from 'core/str';
import Prefetch from 'core/prefetch';
import Notification from 'core/notification';
import {setUserPreference} from 'core_user/repository';
/**
* Set up the contentbank views.
*
* @method init
*/
export const init = () => {
const contentBank = document.querySelector(selectors.regions.contentbank);
Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);
Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);
registerListenerEvents(contentBank);
};
/**
* Register contentbank related event listeners.
*
* @method registerListenerEvents
* @param {HTMLElement} contentBank The DOM node of the content bank
*/
const registerListenerEvents = (contentBank) => {
contentBank.addEventListener('click', e => {
const viewList = contentBank.querySelector(selectors.actions.viewlist);
const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);
const fileArea = contentBank.querySelector(selectors.regions.filearea);
const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);
// View as Grid button.
if (e.target.closest(selectors.actions.viewgrid)) {
contentBank.classList.remove('view-list');
contentBank.classList.add('view-grid');
if (fileArea && shownItems) {
fileArea.setAttribute('role', 'list');
shownItems.forEach(listItem => {
listItem.setAttribute('role', 'listitem');
listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));
});
const heading = fileArea.querySelector(selectors.elements.heading);
if (heading) {
heading.removeAttribute('role');
heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));
}
}
viewGrid.classList.add('active');
viewList.classList.remove('active');
setViewListPreference(false);
return;
}
// View as List button.
if (e.target.closest(selectors.actions.viewlist)) {
contentBank.classList.remove('view-grid');
contentBank.classList.add('view-list');
if (fileArea && shownItems) {
fileArea.setAttribute('role', 'table');
shownItems.forEach(listItem => {
listItem.setAttribute('role', 'row');
listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell'));
});
const heading = fileArea.querySelector(selectors.elements.heading);
if (heading) {
heading.setAttribute('role', 'row');
heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader'));
}
}
viewList.classList.add('active');
viewGrid.classList.remove('active');
setViewListPreference(true);
return;
}
if (fileArea && shownItems) {
// Sort by file name alphabetical
const sortByName = e.target.closest(selectors.actions.sortname);
if (sortByName) {
const ascending = updateSortButtons(contentBank, sortByName);
updateSortOrder(fileArea, shownItems, 'data-file', ascending);
return;
}
// Sort by uses.
const sortByUses = e.target.closest(selectors.actions.sortuses);
if (sortByUses) {
const ascending = updateSortButtons(contentBank, sortByUses);
updateSortOrder(fileArea, shownItems, 'data-uses', ascending);
return;
}
// Sort by date.
const sortByDate = e.target.closest(selectors.actions.sortdate);
if (sortByDate) {
const ascending = updateSortButtons(contentBank, sortByDate);
updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);
return;
}
// Sort by size.
const sortBySize = e.target.closest(selectors.actions.sortsize);
if (sortBySize) {
const ascending = updateSortButtons(contentBank, sortBySize);
updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);
return;
}
// Sort by type.
const sortByType = e.target.closest(selectors.actions.sorttype);
if (sortByType) {
const ascending = updateSortButtons(contentBank, sortByType);
updateSortOrder(fileArea, shownItems, 'data-type', ascending);
return;
}
// Sort by author.
const sortByAuthor = e.target.closest(selectors.actions.sortauthor);
if (sortByAuthor) {
const ascending = updateSortButtons(contentBank, sortByAuthor);
updateSortOrder(fileArea, shownItems, 'data-author', ascending);
}
return;
}
});
};
/**
* Set the contentbank user preference in list view
*
* @param {Bool} viewList view ContentBank as list.
* @return {Promise} Repository promise.
*/
const setViewListPreference = function(viewList) {
// If the given status is not hidden, the preference has to be deleted with a null value.
if (viewList === false) {
viewList = null;
}
return setUserPreference('core_contentbank_view_list', viewList)
.catch(Notification.exception);
};
/**
* Update the sort button view.
*
* @method updateSortButtons
* @param {HTMLElement} contentBank The DOM node of the contentbank button
* @param {HTMLElement} sortButton The DOM node of the sort button
* @return {Bool} sort ascending
*/
const updateSortButtons = (contentBank, sortButton) => {
const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);
sortButtons.forEach((button) => {
if (button !== sortButton) {
button.classList.remove('dir-asc');
button.classList.remove('dir-desc');
button.classList.add('dir-none');
button.closest(selectors.elements.cell).setAttribute('aria-sort', 'none');
updateButtonTitle(button, false);
}
});
let ascending = true;
if (sortButton.classList.contains('dir-none')) {
sortButton.classList.remove('dir-none');
sortButton.classList.add('dir-asc');
sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');
} else if (sortButton.classList.contains('dir-asc')) {
sortButton.classList.remove('dir-asc');
sortButton.classList.add('dir-desc');
sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'descending');
ascending = false;
} else if (sortButton.classList.contains('dir-desc')) {
sortButton.classList.remove('dir-desc');
sortButton.classList.add('dir-asc');
sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');
}
updateButtonTitle(sortButton, ascending);
return ascending;
};
/**
* Update the button title.
*
* @method updateButtonTitle
* @param {HTMLElement} button Button to update
* @param {Bool} ascending Sort direction
* @return {Promise} string promise
*/
const updateButtonTitle = (button, ascending) => {
const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');
return getString(button.dataset.string, 'contentbank')
.then(columnName => {
return getString(sortString, 'core', columnName);
})
.then(sortByString => {
button.setAttribute('title', sortByString);
return sortByString;
})
.catch();
};
/**
* Update the sort order of the itemlist and update the DOM
*
* @method updateSortOrder
* @param {HTMLElement} fileArea the Dom container for the itemlist
* @param {Array} itemList Nodelist of Dom elements
* @param {String} attribute the attribut to sort on
* @param {Bool} ascending Sort Ascending
*/
const updateSortOrder = (fileArea, itemList, attribute, ascending) => {
const sortList = [].slice.call(itemList).sort(function(a, b) {
let aa = a.getAttribute(attribute);
let bb = b.getAttribute(attribute);
if (!isNaN(aa)) {
aa = parseInt(aa);
bb = parseInt(bb);
}
if (ascending) {
return aa > bb ? 1 : -1;
} else {
return aa < bb ? 1 : -1;
}
});
sortList.forEach(listItem => fileArea.appendChild(listItem));
};