Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

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