Файловый менеджер - Редактировать - /home/harasnat/www/learning/question/bank/columnsortorder/amd/src/user_actions.js
Назад
// 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/>. /** * Javascript for customising the user's view of the question bank * * @module qbank_columnsortorder/user_actions * @copyright 2021 Catalyst IT Australia Pty Ltd * @author Ghaly Marc-Alexandre <marc-alexandreghaly@catalyst-ca.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as actions from 'qbank_columnsortorder/actions'; import * as repository from 'qbank_columnsortorder/repository'; import {get_string as getString} from 'core/str'; import ModalEvents from 'core/modal_events'; import ModalSaveCancel from 'core/modal_save_cancel'; import Notification from "core/notification"; import SortableList from 'core/sortable_list'; import Templates from "core/templates"; const SELECTORS = { uiRoot: '.questionbankwindow', moveAction: '.menu-action[data-action=move]', resizeAction: '.menu-action[data-action=resize]', resizeHandle: '.qbank_columnsortorder-action-handle.resize', handleContainer: '.handle-container', headerContainer: '.header-container', tableColumn: identifier => `td[data-columnid="${identifier.replace(/["\\]/g, '\\$&')}"]`, }; /** To track mouse event on a table header */ let currentHeader; /** Current mouse x postion, to track mouse event on a table header */ let currentX; /** Minimum size for the column currently being resized. */ let currentMin; /** * Flag to temporarily prevent move and resize handles from being shown or hidden. * * @type {boolean} */ let suspendShowHideHandles = false; /** * Add handle containers for move and resize handles. * * @param {Element} uiRoot The root element of the quesiton bank UI. * @return {Promise} Resolved after the containers have been added to each column header. */ const addHandleContainers = uiRoot => { return new Promise((resolve) => { const headerContainers = uiRoot.querySelectorAll(SELECTORS.headerContainer); Templates.renderForPromise('qbank_columnsortorder/handle_container', {}) .then(({html, js}) => { headerContainers.forEach(container => { Templates.prependNodeContents(container, html, js); }); resolve(); return headerContainers; }).catch(Notification.exception); }); }; /** * Render move handles in each container. * * This takes a list of the move actions rendered in each column header, and creates a corresponding drag handle for each. * * @param {NodeList} moveActions Menu actions for moving columns. */ const setUpMoveHandles = moveActions => { moveActions.forEach(moveAction => { const header = moveAction.closest('th'); header.classList.add('qbank-sortable-column'); const handleContainer = header.querySelector(SELECTORS.handleContainer); const context = { action: "move", dragtype: "move", target: '', title: moveAction.title, pixicon: "i/dragdrop", pixcomponent: "core", popup: true }; return Templates.renderForPromise('qbank_columnsortorder/action_handle', context) .then(({html, js}) => { Templates.prependNodeContents(handleContainer, html, js); return handleContainer; }).catch(Notification.exception); }); }; /** * Serialise the current column sizes. * * This finds the current width set in each column header's style property, and returns them encoded as a JSON string. * * @param {Element} uiRoot The root element of the quesiton bank UI. * @return {String} JSON array containing a list of objects with column and width properties. */ const serialiseColumnSizes = (uiRoot) => { const columnSizes = []; const tableHeaders = uiRoot.querySelectorAll('th'); tableHeaders.forEach(header => { // Only get the width set via style attribute (set by move action). const width = parseInt(header.style.width); if (!width || isNaN(width)) { return; } columnSizes.push({ column: header.dataset.columnid, width: width }); }); return JSON.stringify(columnSizes); }; /** * Find the minimum width for a header, based on the width of its contents. * * This is to simulate `min-width: min-content;`, which doesn't work on Chrome because * min-width is ignored width `table-layout: fixed;`. * * @param {Element} header The table header * @return {Number} The minimum width in pixels */ const getMinWidth = (header) => { const contents = Array.from(header.querySelector('.header-text').children); const contentWidth = contents.reduce((width, contentElement) => width + contentElement.getBoundingClientRect().width, 0); return Math.ceil(contentWidth); }; /** * Render resize handles in each container. * * This takes a list of the resize actions rendered in each column header, and creates a corresponding drag handle for each. * It also initialises the event handlers for the drag handles and resize modal. * * @param {Element} uiRoot Question bank UI root element. */ const setUpResizeHandles = (uiRoot) => { const resizeActions = uiRoot.querySelectorAll(SELECTORS.resizeAction); resizeActions.forEach(resizeAction => { const headerContainer = resizeAction.closest(SELECTORS.headerContainer); const header = resizeAction.closest(actions.SELECTORS.sortableColumn); const minWidth = getMinWidth(header); if (header.offsetWidth < minWidth) { header.style.width = minWidth + 'px'; } const handleContainer = headerContainer.querySelector(SELECTORS.handleContainer); const context = { action: "resize", target: '', title: resizeAction.title, pixicon: 'i/twoway', pixcomponent: 'core', popup: true }; return Templates.renderForPromise('qbank_columnsortorder/action_handle', context) .then(({html, js}) => { Templates.appendNodeContents(handleContainer, html, js); return handleContainer; }).catch(Notification.exception); }); let moveTracker = false; let currentResizeHandle = null; // Start mouse event on headers. uiRoot.addEventListener('mousedown', e => { currentResizeHandle = e.target.closest(SELECTORS.resizeHandle); // Return if it is not ' resize' button. if (!currentResizeHandle) { return; } // Save current position. currentX = e.pageX; // Find the header. currentHeader = e.target.closest(actions.SELECTORS.sortableColumn); currentMin = getMinWidth(currentHeader); moveTracker = false; suspendShowHideHandles = true; }); // Resize column as the mouse move. document.addEventListener('mousemove', e => { if (!currentHeader || !currentResizeHandle || currentX === 0) { return; } // Prevent text selection as the handle is dragged. document.getSelection().removeAllRanges(); // Adjust the column width according the amount the handle was dragged. const offset = e.pageX - currentX; currentX = e.pageX; const newWidth = currentHeader.offsetWidth + offset; if (newWidth >= currentMin) { currentHeader.style.width = newWidth + 'px'; } moveTracker = true; }); // Set new size when mouse is up. document.addEventListener('mouseup', () => { if (!currentHeader || !currentResizeHandle || currentX === 0) { return; } if (moveTracker) { // If the mouse moved, we are changing the size by drag, so save the change. repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(Notification.exception); } else { // If the mouse didn't move, display a modal to change the size using a form. showResizeModal(currentHeader, uiRoot); } currentMin = null; currentHeader = null; currentResizeHandle = null; currentX = 0; moveTracker = false; suspendShowHideHandles = false; }); }; /** * Event handler for resize actions in each column header. * * This will listen for a click on any resize action, and activate the corresponding resize modal. * * @param {Element} uiRoot Question bank UI root element. */ const setUpResizeActions = uiRoot => { uiRoot.addEventListener('click', (e) => { const resizeAction = e.target.closest(SELECTORS.resizeAction); if (resizeAction) { e.preventDefault(); const currentHeader = resizeAction.closest('th'); showResizeModal(currentHeader, uiRoot); } }); }; /** * Show a modal containing a number input for changing a column width without click-and-drag. * * @param {Element} currentHeader The header element that is being resized. * @param {Element} uiRoot The question bank UI root element. * @returns {Promise<void>} */ const showResizeModal = async(currentHeader, uiRoot) => { const initialWidth = currentHeader.offsetWidth; const minWidth = getMinWidth(currentHeader); const modal = await ModalSaveCancel.create({ title: getString('resizecolumn', 'qbank_columnsortorder', currentHeader.dataset.name), body: Templates.render('qbank_columnsortorder/resize_modal', {width: initialWidth, min: minWidth}), show: true, }); const root = modal.getRoot(); root.on(ModalEvents.cancel, () => { currentHeader.style.width = `${initialWidth}px`; }); root.on(ModalEvents.save, () => { repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(Notification.exception); }); const body = await modal.bodyPromise; const input = body.get(0).querySelector('input'); input.addEventListener('change', e => { const valid = e.target.checkValidity(); e.target.closest('.has-validation').classList.add('was-validated'); if (valid) { const newWidth = e.target.value; currentHeader.style.width = `${newWidth}px`; } }); }; /** * Event handler for move actions in each column header. * * This will listen for a click on any move action, pass the click to the corresponding move handle, causing its modal to be shown. * * @param {Element} uiRoot Question bank UI root element. */ const setUpMoveActions = uiRoot => { uiRoot.addEventListener('click', e => { const moveAction = e.target.closest(SELECTORS.moveAction); if (moveAction) { e.preventDefault(); const sortableColumn = moveAction.closest(actions.SELECTORS.sortableColumn); const moveHandle = sortableColumn.querySelector(actions.SELECTORS.moveHandler); moveHandle.click(); } }); }; /** * Event handler for showing and hiding handles when the mouse is over a column header. * * Implementing this behaviour using the :hover CSS pseudoclass is not sufficient, as the mouse may move over the neighbouring * header while dragging, leading to some odd behaviour. This allows us to suspend the show/hide behaviour while a handle is being * dragged, and so keep the active handle visible until the drag is finished. * * @param {Element} uiRoot Question bank UI root element. */ const setupShowHideHandles = uiRoot => { let shownHeader = null; let tableHead = uiRoot.querySelector('thead'); uiRoot.addEventListener('mouseover', e => { if (suspendShowHideHandles) { return; } const header = e.target.closest(actions.SELECTORS.sortableColumn); if (!header && !shownHeader) { return; } if (!header || header !== shownHeader) { tableHead.querySelector('.show-handles')?.classList.remove('show-handles'); shownHeader = header; if (header) { header.classList.add('show-handles'); } } }); }; /** * Event handler for sortable list DROP event. * * Find all table cells corresponding to the column of the dropped header, and move them to the new position. * * @param {Event} event */ const reorderColumns = event => { // Current header. const header = event.target; // Find the previous sibling of the header, which will be used when moving columns. const insertAfter = header.previousElementSibling; // Move columns. const uiRoot = document.querySelector(SELECTORS.uiRoot); const columns = uiRoot.querySelectorAll(SELECTORS.tableColumn(header.dataset.columnid)); columns.forEach(column => { const row = column.parentElement; if (insertAfter) { // Find the column to insert after. const insertAfterColumn = row.querySelector(SELECTORS.tableColumn(insertAfter.dataset.columnid)); // Insert the column. insertAfterColumn.after(column); } else { // Insert as the first child (first column in the table). row.insertBefore(column, row.firstChild); } }); }; /** * Initialize module * * Add containers for the drag handles to each column header, then render handles, enable show/hide behaviour, set up drag/drop * column sorting, then enable the move and resize modals to be triggered from menu actions. */ export const init = async() => { const uiRoot = document.getElementById('questionscontainer'); await addHandleContainers(uiRoot); setUpMoveHandles(uiRoot.querySelectorAll(SELECTORS.moveAction)); setUpResizeHandles(uiRoot); setupShowHideHandles(uiRoot); const sortableColumns = actions.setupSortableLists(uiRoot.querySelector(actions.SELECTORS.columnList)); sortableColumns.on(SortableList.EVENTS.DROP, reorderColumns); sortableColumns.on(SortableList.EVENTS.DRAGSTART, () => { suspendShowHideHandles = true; }); sortableColumns.on(SortableList.EVENTS.DRAGEND, () => { suspendShowHideHandles = false; }); setUpMoveActions(uiRoot); setUpResizeActions(uiRoot); actions.setupActionButtons(uiRoot); };
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.01 |
proxy
|
phpinfo
|
Настройка