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-alexa
ndreghaly@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.st
ringify(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(Not
ification.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.targe
t.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 lis
t 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 setUpMoveA
ctions(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","inse
rtBefore","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":";;;;;;;;0bAkCMA,iBACM,sBADNA,qBAEU,iCAFVA,uBAGY,mCAHZA,uBAIY,8CAJZA,0BAKe,oBALfA,0BAMe,oBANfA,sBAOWC,wCAAmCA,WAAWC,QAAQ,SAAU,kBAI7EC,cAGAC,SAGAC,WAOAC,wBAAyB,QA2DvBC,qBAAwBC,eACpBC,YAAc,UACCD,OAAOE,iBAAiB,MAChCC,SAAQC,eAEXC,MAAQC,SAASF,OAAOG,MAAMF,OAC/BA,QAASG,MAAMH,QA
GpBJ,YAAYQ,KAAK,CACbC,OAAQN,OAAOO,QAAQC,SACvBP,MAAOA,WAGRQ,KAAKC,UAAUb,cAYpBc,YAAeX,eAEXY,aADWC,MAAMC,KAAKd,OAAOe,cAAc,gBAAgBC,UACnCC,QAAO,CAAChB,MAAOiB,iBAAmBjB,MAAQiB,eAAeC,wBAAwBlB,OAAO,UAC/GmB,KAAKC,KAAKT,eAuHfU,gBAAkBC,MAAMhC,cAAeK,gBACnC4B,aAAejC,cAAckC,YAC7BC,SAAWf,YAAYpB,eAEvBoC,YAAcC,2BAAgBC,OAAO,CACvCC,OAAO,mBAAU,eAAgB,wBAAyBvC,cAAcgB,QAAQwB,MAChFC,KAAMC,mBAAUC,OAAO,qCAAsC,CAACjC,MAAOuB,aAAcW,IAAKT,WACxFU,MAAM,IAEJC,KAAOV,MAAMW,UACnBD,KAAKE,GAAGC,sBAAYC,QAAQ,KACxBlD,cAAcY,MAAMF,gBAAWuB,sBAEnCa,KAAKE,GAAGC,sBAAYE,MAAM,KACtBC,WAAWC,cAAcjD,qBAAqBC,SAASiD,MAAMC,sBAAaC,qBAG3DpB,MAAMqB,aACNC,IAAI,GAAGlC,cAAc,SAElCmC,iBAAiB,UAAUC,UACvBC,MAAQD,EAAEE,OAAOC,mBACvBH,EAAEE,OAAOE,QAAQ,mBAAmBC,UAAUC,IAAI,iBAC9CL,MAAO,OACDM,SAAWP,EAAEE,OAAOM,MAC1BpE,cAAcY,MAAMF,gBAAWyD,oBA6DrCE,eAAiBC,cAEb7D,OAAS6D,MAAMR,OAEfS,YAAc9D,OAAO+D,uBAEZC,SAASjD,cAAc3B,kBACfU,iBAAiBV,sBAAsBY,OAAOO,QAAQC,WACrET,SAAQO,eACN2D,IAAM3D,OAAO4D,iBACfJ,YAAa,CAEaG,IAAIlD,cAAc3B,sBAAsB0E,YAAYvD,QAAQC,WAEpE2D,MAAM7D,aAGxB2D,IAAIG,aAAa9D,OAAQ2D,IAAII
,8BAWrB9C,gBACV3B,OAASoE,SAASM,eAAe,2BA1Tf1E,CAAAA,QACjB,IAAI2E,SAASC,gBACVC,iBAAmB7E,OAAOE,iBAAiBV,8CACvCsF,iBAAiB,yCAA0C,IAChEC,MAAKC,WAACC,KAACA,KAADC,GAAOA,gBACVL,iBAAiB1E,SAAQgF,+BACXC,oBAAoBD,UAAWF,KAAMC,OAEnDN,UACOC,oBACR5B,MAAMC,sBAAaC,cAiTxBkC,CAAoBrF,QACTA,OAAOE,iBAAiBV,sBAtS7BW,SAAQmF,mBACVlF,OAASkF,WAAW3B,QAAQ,MAClCvD,OAAOwD,UAAUC,IAAI,+BACf0B,gBAAkBnF,OAAOe,cAAc3B,2BACvCgG,QAAU,CACZC,OAAQ,OACRC,SAAU,OACVjC,OAAQ,GACRvB,MAAOoD,WAAWpD,MAClByD,QAAS,aACTC,aAAc,OACdC,OAAO,UAEJxD,mBAAUyC,iBAAiB,sCAAuCU,SACpET,MAAKe,YAACb,KAACA,KAADC,GAAOA,oCACAE,oBAAoBG,gBAAiBN,KAAMC,IAC9CK,mBACRtC,MAAMC,sBAAaC,cAoDNnD,CAAAA,SACFA,OAAOE,iBAAiBV,wBAChCW,SAAQ4F,qBACZC,gBAAkBD,aAAapC,QAAQnE,2BACvCY,OAAS2F,aAAapC,QAAQsC,QAAQzG,UAAU0G,gBAChDpE,SAAWf,YAAYX,QACzBA,OAAOyB,YAAcC,WACrB1B,OAAOG,MAAMF,MAAQyB,SAAW,YAE9ByD,gBAAkBS,gBAAgB7E,cAAc3B,2BAChDgG,QAAU,CACZC,OAAQ,SACRhC,OAAQ,GACRvB,MAAO6D,aAAa7D,MACpByD,QAAS,WACTC,aAAc,OACdC,OAAO,UAEJxD,mBAAUyC,iBAAiB,sCAAuCU,SACpET,MAAKoB,YAAClB,KAACA,KAADC,GAAOA,oCACAkB,mBAAmBb,gBAAiBN,KAA
MC,IAC7CK,mBACRtC,MAAMC,sBAAaC,kBAG1BkD,aAAc,EACdC,oBAAsB,KAE1BtG,OAAOsD,iBAAiB,aAAaC,IACjC+C,oBAAsB/C,EAAEE,OAAOE,QAAQnE,wBAElC8G,sBAIL1G,SAAW2D,EAAEgD,MAEb5G,cAAgB4D,EAAEE,OAAOE,QAAQsC,QAAQzG,UAAU0G,gBACnDrG,WAAakB,YAAYpB,eACzB0G,aAAc,EACdvG,wBAAyB,MAI7BsE,SAASd,iBAAiB,aAAaC,QAC9B5D,gBAAkB2G,qBAAoC,IAAb1G,gBAK9CwE,SAASoC,eAAeC,wBAGlBC,OAASnD,EAAEgD,MAAQ3G,SACzBA,SAAW2D,EAAEgD,YACPzC,SAAWnE,cAAckC,YAAc6E,OACzC5C,UAAYjE,aACZF,cAAcY,MAAMF,MAAQyD,SAAW,MAE3CuC,aAAc,KAIlBjC,SAASd,iBAAiB,WAAW,KAC5B3D,eAAkB2G,qBAAoC,IAAb1G,WAG1CyG,YAEAtD,WAAWC,cAAcjD,qBAAqBC,SAASiD,MAAMC,sBAAaC,WAG1EzB,gBAAgB/B,cAAeK,QAEnCH,WAAa,KACbF,cAAgB,KAChB2G,oBAAsB,KACtB1G,SAAW,EACXyG,aAAc,EACdvG,wBAAyB,OAmJ7B6G,CAAmB3G,QA5DMA,CAAAA,aACrB4G,YAAc,KACdC,UAAY7G,OAAOmB,cAAc,SACrCnB,OAAOsD,iBAAiB,aAAaC,OAC7BzD,oCAGEM,OAASmD,EAAEE,OAAOE,QAAQsC,QAAQzG,UAAU0G,2CAC7C9F,QAAWwG,eAGXxG,QAAUA,SAAWwG,4CACtBC,UAAU1F,cAAc,yEAAkByC,UAAUkD,OAAO,gBAC3DF,YAAcxG,OACVA,QACAA,OAAOwD,UAAUC,IAAI,sBA8CjCkD,CAAqB/G,cACfgH,gBAAkBf,QAAQgB,mBAAmBjH,OAAOmB,cAAc8E,QAAQzG,
UAAU0H,aAC1FF,gBAAgBrE,GAAGwE,uBAAaC,OAAOC,KAAMrD,gBAC7CgD,gBAAgBrE,GAAGwE,uBAAaC,OAAOE,WAAW,KAC9CxH,wBAAyB,KAE7BkH,gBAAgBrE,GAAGwE,uBAAaC,OAAOG,SAAS,KAC5CzH,wBAAyB,KAzFRE,CAAAA,SACrBA,OAAOsD,iBAAiB,SAASC,UACvB+B,WAAa/B,EAAEE,OAAOE,QAAQnE,sBAChC8F,aACA/B,EAAEiE,iBACqBlC,WAAW3B,QAAQsC,QAAQzG,UAAU0G,gBAC1B/E,cAAc8E,QAAQzG,UAAUiI,aACvDC,aAoFnBC,CAAiB3H,QAlJMA,CAAAA,SACvBA,OAAOsD,iBAAiB,SAAUC,UACxBwC,aAAexC,EAAEE,OAAOE,QAAQnE,2BAClCuG,aAAc,CACdxC,EAAEiE,uBACI7H,cAAgBoG,aAAapC,QAAQ,MAC3CjC,gBAAgB/B,cAAeK,aA6IvC4H,CAAmB5H,QACnBiG,QAAQ4B,mBAAmB7H"}