From 63894ec2fe5cc24fedff29252ed0bca37f90e64f Mon Sep 17 00:00:00 2001 From: Mark Johnson <mark.johnson@catalyst-eu.net> Date: Tue, 5 Sep 2023 10:31:10 +0100 Subject: [PATCH] MDL-74054 qbank_columnsortorder: Progressively enhance question bank actions This modifies the question_data fragment used by the filter code to make its parameters closer to the URL parameters of the question/edit.php page. This Allows us to progressively enhance the add, remove and reset actions on the question bank page, using this same fragment to reload the question table after each change. This re-uses the same actions.js module used for enhancing these actions on the qbank_columnsortorder admin screen. --- mod/quiz/lib.php | 9 +++- question/amd/build/filter.min.js | 2 +- question/amd/build/filter.min.js.map | 2 +- question/amd/src/filter.js | 41 +++++++++--------- .../columnsortorder/amd/build/actions.min.js | 2 +- .../amd/build/actions.min.js.map | 2 +- .../amd/build/user_actions.min.js | 2 +- .../amd/build/user_actions.min.js.map | 2 +- .../bank/columnsortorder/amd/src/actions.js | 20 ++++++++- .../columnsortorder/amd/src/user_actions.js | 3 +- .../local/bank/column_action_remove.php | 2 +- .../local/bank/filter_condition_manager.php | 25 +++++++---- question/editlib.php | 35 +++++++--------- question/lib.php | 42 ++++++++----------- 14 files changed, 106 insertions(+), 83 deletions(-) diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 5c3130d6b56..6fd6e221564 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -2549,15 +2549,22 @@ function mod_quiz_output_fragment_question_data(array $args): string { $thispageurl = new \moodle_url('/mod/quiz/edit.php', ['cmid' => $cmid]); $thiscontext = \context_module::instance($cmid); $contexts = new \core_question\local\bank\question_edit_contexts($thiscontext); + $defaultcategory = question_make_default_categories($contexts->all()); + $params['cat'] = implode(',', [$defaultcategory->id, $defaultcategory->contextid]); + $course = get_course($params['courseid']); [, $cm] = get_module_from_cmid($cmid); + $params['tabname'] = 'questions'; // Custom question bank View. $viewclass = clean_param($args['view'], PARAM_NOTAGS); $questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $params, $extraparams); // Question table. - return $questionbank->display_questions_table(); + $questionbank->add_standard_search_conditions(); + ob_start(); + $questionbank->display_question_list(); + return ob_get_clean(); } /** diff --git a/question/amd/build/filter.min.js b/question/amd/build/filter.min.js index 113eed8c1db..5bc3fb4438f 100644 --- a/question/amd/build/filter.min.js +++ b/question/amd/build/filter.min.js @@ -5,6 +5,6 @@ define("core_question/filter",["exports","core/datafilter","core/notification"," * @module core_question/filter * @copyright 2021 Tomo Tsuyuki <tomotsuyuki@catalyst-au.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_datafilter=_interopRequireDefault(_datafilter),_notification=_interopRequireDefault(_notification),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_fragment=_interopRequireDefault(_fragment);_exports.init=(filterRegionId,defaultcourseid,defaultcategoryid,perpage,contextId,component,callback,view,cmid,pagevars,extraparams)=>{var _document$querySelect,_document$querySelect2;const SELECTORS_QUESTION_CONTAINER_ID="#questionscontainer",SELECTORS_QUESTION_TABLE="#questionscontainer table",SELECTORS_SORT_LINK="#questionscontainer div.sorters a",SELECTORS_PAGINATION_LINK="#questionscontainer a[href].page-link",SELECTORS_LASTCHANGED_FIELD="#questionsubmit input[name=lastchanged]",SELECTORS_EDIT_SWITCH=".editmode-switch-form input[name=setmode]",SELECTORS_EDIT_SWITCH_URL=".editmode-switch-form input[name=pageurl]",filterSet=document.querySelector("#".concat(filterRegionId)),filterCondition={cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},tabname:"questions"},defaultSort=null===(_document$querySelect=document.querySelector(SELECTORS_QUESTION_TABLE))||void 0===_document$querySelect||null===(_document$querySelect2=_document$querySelect.dataset)||void 0===_document$querySelect2?void 0:_document$querySelect2.defaultsort;defaultSort&&(filterCondition.sortData=JSON.parse(defaultSort));const coreFilter=new _datafilter.default(filterSet,((filterdata,pendingPromise)=>{var _document$querySelect3,_document$querySelect4;filterdata&&(filterCondition.jointype=parseInt(filterSet.dataset.filterverb,10),delete filterdata.jointype,filterCondition.filter=filterdata,0!==Object.keys(filterdata).length&&(isNaN(filterCondition.jointype)||(filterdata.jointype=filterCondition.jointype),updateUrlParams(filterdata)));const viewData={view:view,cmid:cmid,filtercondition:JSON.stringify(filterCondition),extraparams:extraparams,lastchanged:null!==(_document$querySelect3=null===(_document$querySelect4=document.querySelector(SELECTORS_LASTCHANGED_FIELD))||void 0===_document$querySelect4?void 0:_document$querySelect4.value)&&void 0!==_document$querySelect3?_document$querySelect3:null};_fragment.default.loadFragment(component,callback,contextId,viewData).then(((questionhtml,jsfooter)=>{const questionscontainer=document.querySelector(SELECTORS_QUESTION_CONTAINER_ID);return void 0===questionhtml&&(questionhtml=""),void 0===jsfooter&&(jsfooter=""),_templates.default.replaceNodeContents(questionscontainer,questionhtml,jsfooter),pendingPromise&&pendingPromise.resolve(),{questionhtml:questionhtml,jsfooter:jsfooter}})).catch(_notification.default.exception)}));coreFilter.activeFilters={},coreFilter.init();const updateUrlParams=filters=>{const url=new URL(location.href),filterQuery=JSON.stringify(filters);url.searchParams.set("filter",filterQuery),history.pushState(filters,"",url);const editSwitch=document.querySelector(SELECTORS_EDIT_SWITCH);if(editSwitch){const editSwitchUrlInput=document.querySelector(SELECTORS_EDIT_SWITCH_URL),editSwitchUrl=new URL(editSwitchUrlInput.value);editSwitchUrl.searchParams.set("filter",filterQuery),editSwitchUrlInput.value=editSwitchUrl,editSwitch.dataset.pageurl=editSwitchUrl}};let initialFilters;document.addEventListener("click",(e=>{const sortableLink=e.target.closest(SELECTORS_SORT_LINK),paginationLink=e.target.closest(SELECTORS_PAGINATION_LINK),clearLink=e.target.closest(_selectors.default.filterset.actions.resetFilters);if(sortableLink){e.preventDefault();const oldSort=filterCondition.sortdata;filterCondition.sortdata={},filterCondition.sortdata[sortableLink.dataset.sortname]=sortableLink.dataset.sortorder;for(const sortname in oldSort)sortname!==sortableLink.dataset.sortname&&(filterCondition.sortdata[sortname]=oldSort[sortname]);filterCondition.qpage=0,coreFilter.updateTableFromFilter()}if(paginationLink){e.preventDefault();const paginationURL=new URL(paginationLink.getAttribute("href")),qpage=paginationURL.searchParams.get("qpage");null!==paginationURL.search&&(filterCondition.qpage=qpage,coreFilter.updateTableFromFilter())}clearLink&&(()=>{const queryString=location.search,urlParams=new URLSearchParams(queryString);if(urlParams.has("cmid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("cmid",urlParams.get("cmid")),history.pushState({},"",cleanedUrl)}if(urlParams.has("courseid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("courseid",urlParams.get("courseid")),history.pushState({},"",cleanedUrl)}})()}));let jointype=null;if((pagevars=JSON.parse(pagevars)).filter&&(initialFilters=pagevars.filter,pagevars.jointype&&(jointype=pagevars.jointype)),0!==Object.entries(initialFilters).length){const emptyFilterRow=filterSet.querySelector(_selectors.default.filterset.regions.emptyFilterRow);emptyFilterRow&&emptyFilterRow.remove();let rowcount=0;for(const urlFilter in initialFilters){if("jointype"===urlFilter){jointype=initialFilters[urlFilter];continue}rowcount+=1;const filterdata={filtertype:urlFilter,values:initialFilters[urlFilter].values,jointype:initialFilters[urlFilter].jointype,filteroptions:initialFilters[urlFilter].filteroptions,rownum:rowcount};coreFilter.addFilterRow(filterdata)}coreFilter.filterSet.dataset.filterverb=jointype;const join=coreFilter.filterSet.querySelector(_selectors.default.filterset.fields.join);join.querySelectorAll('option:not([value="'.concat(jointype,'"])')).forEach((option=>option.remove())),join.disabled=!0}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_datafilter=_interopRequireDefault(_datafilter),_notification=_interopRequireDefault(_notification),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_fragment=_interopRequireDefault(_fragment);_exports.init=(filterRegionId,defaultcourseid,defaultcategoryid,perpage,contextId,component,callback,view,cmid,pagevars,extraparams)=>{var _document$querySelect,_document$querySelect2,_document$querySelect3,_document$querySelect4;const SELECTORS_QUESTION_CONTAINER_ID="#questionscontainer",SELECTORS_QUESTION_TABLE="#questionscontainer table",SELECTORS_SORT_LINK="#questionscontainer div.sorters a",SELECTORS_PAGINATION_LINK="#questionscontainer a[href].page-link",SELECTORS_LASTCHANGED_FIELD="#questionsubmit input[name=lastchanged]",SELECTORS_EDIT_SWITCH=".editmode-switch-form input[name=setmode]",SELECTORS_EDIT_SWITCH_URL=".editmode-switch-form input[name=pageurl]",filterSet=document.querySelector("#".concat(filterRegionId)),viewData={view:view,cmid:cmid,cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},extraparams:extraparams,lastchanged:null!==(_document$querySelect=null===(_document$querySelect2=document.querySelector(SELECTORS_LASTCHANGED_FIELD))||void 0===_document$querySelect2?void 0:_document$querySelect2.value)&&void 0!==_document$querySelect?_document$querySelect:null};let sortData={};const defaultSort=null===(_document$querySelect3=document.querySelector(SELECTORS_QUESTION_TABLE))||void 0===_document$querySelect3||null===(_document$querySelect4=_document$querySelect3.dataset)||void 0===_document$querySelect4?void 0:_document$querySelect4.defaultsort;defaultSort&&(sortData=JSON.parse(defaultSort));const coreFilter=new _datafilter.default(filterSet,((filterdata,pendingPromise)=>{filterdata&&(viewData.jointype=parseInt(filterSet.dataset.filterverb,10),delete filterdata.jointype,viewData.filter=filterdata,0!==Object.keys(filterdata).length&&(isNaN(viewData.jointype)||(filterdata.jointype=viewData.jointype),updateUrlParams(filterdata))),viewData.filter=JSON.stringify(filterdata),viewData.sortdata=JSON.stringify(sortData),_fragment.default.loadFragment(component,callback,contextId,viewData).then(((questionhtml,jsfooter)=>{const questionscontainer=document.querySelector(SELECTORS_QUESTION_CONTAINER_ID);return void 0===questionhtml&&(questionhtml=""),void 0===jsfooter&&(jsfooter=""),_templates.default.replaceNode(questionscontainer,questionhtml,jsfooter),pendingPromise&&pendingPromise.resolve(),{questionhtml:questionhtml,jsfooter:jsfooter}})).catch(_notification.default.exception)}));coreFilter.activeFilters={},coreFilter.init();const updateUrlParams=filters=>{const url=new URL(location.href),filterQuery=JSON.stringify(filters);url.searchParams.set("filter",filterQuery),history.pushState(filters,"",url);const editSwitch=document.querySelector(SELECTORS_EDIT_SWITCH);if(editSwitch){const editSwitchUrlInput=document.querySelector(SELECTORS_EDIT_SWITCH_URL),editSwitchUrl=new URL(editSwitchUrlInput.value);editSwitchUrl.searchParams.set("filter",filterQuery),editSwitchUrlInput.value=editSwitchUrl,editSwitch.dataset.pageurl=editSwitchUrl}};let initialFilters;document.addEventListener("click",(e=>{const sortableLink=e.target.closest(SELECTORS_SORT_LINK),paginationLink=e.target.closest(SELECTORS_PAGINATION_LINK),clearLink=e.target.closest(_selectors.default.filterset.actions.resetFilters);if(sortableLink){e.preventDefault();const oldSort=sortData;sortData={},sortData[sortableLink.dataset.sortname]=sortableLink.dataset.sortorder;for(const sortname in oldSort)sortname!==sortableLink.dataset.sortname&&(sortData[sortname]=oldSort[sortname]);viewData.qpage=0,coreFilter.updateTableFromFilter()}if(paginationLink){e.preventDefault();const paginationURL=new URL(paginationLink.getAttribute("href")),qpage=paginationURL.searchParams.get("qpage");null!==paginationURL.search&&(viewData.qpage=qpage,coreFilter.updateTableFromFilter())}clearLink&&(()=>{const queryString=location.search,urlParams=new URLSearchParams(queryString);if(urlParams.has("cmid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("cmid",urlParams.get("cmid")),history.pushState({},"",cleanedUrl)}if(urlParams.has("courseid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("courseid",urlParams.get("courseid")),history.pushState({},"",cleanedUrl)}})()}));let jointype=null;if((pagevars=JSON.parse(pagevars)).filter&&(initialFilters=pagevars.filter,pagevars.jointype&&(jointype=pagevars.jointype)),0!==Object.entries(initialFilters).length){const emptyFilterRow=filterSet.querySelector(_selectors.default.filterset.regions.emptyFilterRow);emptyFilterRow&&emptyFilterRow.remove();let rowcount=0;for(const urlFilter in initialFilters){if("jointype"===urlFilter){jointype=initialFilters[urlFilter];continue}rowcount+=1;const filterdata={filtertype:urlFilter,values:initialFilters[urlFilter].values,jointype:initialFilters[urlFilter].jointype,filteroptions:initialFilters[urlFilter].filteroptions,rownum:rowcount};coreFilter.addFilterRow(filterdata)}coreFilter.filterSet.dataset.filterverb=jointype;const join=coreFilter.filterSet.querySelector(_selectors.default.filterset.fields.join);join.querySelectorAll('option:not([value="'.concat(jointype,'"])')).forEach((option=>option.remove())),join.disabled=!0}}})); //# sourceMappingURL=filter.min.js.map \ No newline at end of file diff --git a/question/amd/build/filter.min.js.map b/question/amd/build/filter.min.js.map index 58994b35155..e33121de84e 100644 --- a/question/amd/build/filter.min.js.map +++ b/question/amd/build/filter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"filter.min.js","sources":["../src/filter.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 * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CoreFilter from 'core/datafilter';\nimport Notification from 'core/notification';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport Fragment from 'core/fragment';\n\n/**\n * Initialise the question bank filter on the element with the given id.\n *\n * @param {String} filterRegionId ID of the HTML element containing the filters.\n * @param {String} defaultcourseid Course ID for the default course to pass back to the view.\n * @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view.\n * @param {Number} perpage The number of questions to display per page.\n * @param {Number} contextId Context ID of the question bank view.\n * @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question)\n * @param {string} callback Name of the callback for the fragment API (e.g question_data)\n * @param {string} view The class name of the question bank view class used for this page.\n * @param {Number} cmid If we are in an activitiy, the course module ID.\n * @param {string} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {string} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.\n */\nexport const init = (\n filterRegionId,\n defaultcourseid,\n defaultcategoryid,\n perpage,\n contextId,\n component,\n callback,\n view,\n cmid,\n pagevars,\n extraparams\n) => {\n\n const SELECTORS = {\n QUESTION_CONTAINER_ID: '#questionscontainer',\n QUESTION_TABLE: '#questionscontainer table',\n SORT_LINK: '#questionscontainer div.sorters a',\n PAGINATION_LINK: '#questionscontainer a[href].page-link',\n LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]',\n BULK_ACTIONS: '#bulkactionsui-container input',\n MENU_ACTIONS: '.menu-action',\n EDIT_SWITCH: '.editmode-switch-form input[name=setmode]',\n EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const filterCondition = {\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\n tabname: 'questions',\n };\n\n const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;\n if (defaultSort) {\n filterCondition.sortData = JSON.parse(defaultSort);\n }\n\n /**\n * Retrieve table data.\n *\n * @param {Object} filterdata data\n * @param {Promise} pendingPromise pending promise\n */\n const applyFilter = (filterdata, pendingPromise) => {\n // Reload the questions based on the specified filters. If no filters are provided,\n // use the default category filter condition.\n if (filterdata) {\n // Main join types.\n filterCondition.jointype = parseInt(filterSet.dataset.filterverb, 10);\n delete filterdata.jointype;\n // Retrieve filter info.\n filterCondition.filter = filterdata;\n if (Object.keys(filterdata).length !== 0) {\n if (!isNaN(filterCondition.jointype)) {\n filterdata.jointype = filterCondition.jointype;\n }\n updateUrlParams(filterdata);\n }\n }\n // Load questions for first page.\n const viewData = {\n view: view,\n cmid: cmid,\n filtercondition: JSON.stringify(filterCondition),\n extraparams: extraparams,\n lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null\n };\n Fragment.loadFragment(component, callback, contextId, viewData)\n // Render questions for first page and pagination.\n .then((questionhtml, jsfooter) => {\n const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);\n if (questionhtml === undefined) {\n questionhtml = '';\n }\n if (jsfooter === undefined) {\n jsfooter = '';\n }\n Templates.replaceNodeContents(questionscontainer, questionhtml, jsfooter);\n // Resolve filter promise.\n if (pendingPromise) {\n pendingPromise.resolve();\n }\n return {questionhtml, jsfooter};\n })\n .catch(Notification.exception);\n };\n\n // Init core filter processor with apply callback.\n const coreFilter = new CoreFilter(filterSet, applyFilter);\n coreFilter.activeFilters = {}; // Unset useless courseid filter.\n coreFilter.init();\n\n /**\n * Update URL Param based upon the current filter.\n *\n * @param {Object} filters Active filters.\n */\n const updateUrlParams = (filters) => {\n const url = new URL(location.href);\n const filterQuery = JSON.stringify(filters);\n url.searchParams.set('filter', filterQuery);\n history.pushState(filters, '', url);\n const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);\n if (editSwitch) {\n const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);\n const editSwitchUrl = new URL(editSwitchUrlInput.value);\n editSwitchUrl.searchParams.set('filter', filterQuery);\n editSwitchUrlInput.value = editSwitchUrl;\n editSwitch.dataset.pageurl = editSwitchUrl;\n }\n };\n\n /**\n * Cleans URL parameters.\n */\n const cleanUrlParams = () => {\n const queryString = location.search;\n const urlParams = new URLSearchParams(queryString);\n if (urlParams.has('cmid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('cmid', urlParams.get('cmid'));\n history.pushState({}, '', cleanedUrl);\n }\n\n if (urlParams.has('courseid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('courseid', urlParams.get('courseid'));\n history.pushState({}, '', cleanedUrl);\n }\n };\n\n // Add listeners for the sorting, paging and clear actions.\n document.addEventListener('click', e => {\n const sortableLink = e.target.closest(SELECTORS.SORT_LINK);\n const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);\n const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);\n if (sortableLink) {\n e.preventDefault();\n const oldSort = filterCondition.sortdata;\n filterCondition.sortdata = {};\n filterCondition.sortdata[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;\n for (const sortname in oldSort) {\n if (sortname !== sortableLink.dataset.sortname) {\n filterCondition.sortdata[sortname] = oldSort[sortname];\n }\n }\n filterCondition.qpage = 0;\n coreFilter.updateTableFromFilter();\n }\n if (paginationLink) {\n e.preventDefault();\n const paginationURL = new URL(paginationLink.getAttribute(\"href\"));\n const qpage = paginationURL.searchParams.get('qpage');\n if (paginationURL.search !== null) {\n filterCondition.qpage = qpage;\n coreFilter.updateTableFromFilter();\n }\n }\n if (clearLink) {\n cleanUrlParams();\n }\n });\n\n // Run apply filter at page load.\n pagevars = JSON.parse(pagevars);\n let initialFilters;\n let jointype = null;\n if (pagevars.filter) {\n // Load initial filter based on page vars.\n initialFilters = pagevars.filter;\n if (pagevars.jointype) {\n jointype = pagevars.jointype;\n }\n }\n\n if (Object.entries(initialFilters).length !== 0) {\n // Remove the default empty filter row.\n const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow);\n if (emptyFilterRow) {\n emptyFilterRow.remove();\n }\n\n // Add filters.\n let rowcount = 0;\n for (const urlFilter in initialFilters) {\n if (urlFilter === 'jointype') {\n jointype = initialFilters[urlFilter];\n continue;\n }\n // Add each filter row.\n rowcount += 1;\n const filterdata = {\n filtertype: urlFilter,\n values: initialFilters[urlFilter].values,\n jointype: initialFilters[urlFilter].jointype,\n filteroptions: initialFilters[urlFilter].filteroptions,\n rownum: rowcount\n };\n coreFilter.addFilterRow(filterdata);\n }\n coreFilter.filterSet.dataset.filterverb = jointype;\n\n // Since we must filter by category, it does not make sense to allow the top-level \"match any\" or \"match none\" conditions,\n // as this would exclude the category. Remove those options and disable the select.\n const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join);\n join.querySelectorAll(`option:not([value=\"${jointype}\"])`).forEach((option) => option.remove());\n join.disabled = true;\n }\n};\n"],"names":["filterRegionId","defaultcourseid","defaultcategoryid","perpage","contextId","component","callback","view","cmid","pagevars","extraparams","SELECTORS","filterSet","document","querySelector","filterCondition","cat","courseid","filter","jointype","qpage","qperpage","sortdata","tabname","defaultSort","_document$querySelect","dataset","_document$querySelect2","defaultsort","sortData","JSON","parse","coreFilter","CoreFilter","filterdata","pendingPromise","parseInt","filterverb","Object","keys","length","isNaN","updateUrlParams","viewData","filtercondition","stringify","lastchanged","_document$querySelect4","value","loadFragment","then","questionhtml","jsfooter","questionscontainer","undefined","replaceNodeContents","resolve","catch","Notification","exception","activeFilters","init","filters","url","URL","location","href","filterQuery","searchParams","set","history","pushState","editSwitch","editSwitchUrlInput","editSwitchUrl","pageurl","initialFilters","addEventListener","e","sortableLink","target","closest","paginationLink","clearLink","Selectors","filterset","actions","resetFilters","preventDefault","oldSort","sortname","sortorder","updateTableFromFilter","paginationURL","getAttribute","get","search","queryString","urlParams","URLSearchParams","has","cleanedUrl","replace","cleanUrlParams","entries","emptyFilterRow","regions","remove","rowcount","urlFilter","filtertype","values","filteroptions","rownum","addFilterRow","join","fields","querySelectorAll","forEach","option","disabled"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"filter.min.js","sources":["../src/filter.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 * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CoreFilter from 'core/datafilter';\nimport Notification from 'core/notification';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport Fragment from 'core/fragment';\n\n/**\n * Initialise the question bank filter on the element with the given id.\n *\n * @param {String} filterRegionId ID of the HTML element containing the filters.\n * @param {String} defaultcourseid Course ID for the default course to pass back to the view.\n * @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view.\n * @param {Number} perpage The number of questions to display per page.\n * @param {Number} contextId Context ID of the question bank view.\n * @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question)\n * @param {string} callback Name of the callback for the fragment API (e.g question_data)\n * @param {string} view The class name of the question bank view class used for this page.\n * @param {Number} cmid If we are in an activitiy, the course module ID.\n * @param {string} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {string} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.\n */\nexport const init = (\n filterRegionId,\n defaultcourseid,\n defaultcategoryid,\n perpage,\n contextId,\n component,\n callback,\n view,\n cmid,\n pagevars,\n extraparams\n) => {\n\n const SELECTORS = {\n QUESTION_CONTAINER_ID: '#questionscontainer',\n QUESTION_TABLE: '#questionscontainer table',\n SORT_LINK: '#questionscontainer div.sorters a',\n PAGINATION_LINK: '#questionscontainer a[href].page-link',\n LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]',\n BULK_ACTIONS: '#bulkactionsui-container input',\n MENU_ACTIONS: '.menu-action',\n EDIT_SWITCH: '.editmode-switch-form input[name=setmode]',\n EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const viewData = {\n view: view,\n cmid: cmid,\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\n extraparams: extraparams,\n lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null,\n };\n\n let sortData = {};\n const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;\n if (defaultSort) {\n sortData = JSON.parse(defaultSort);\n }\n\n /**\n * Retrieve table data.\n *\n * @param {Object} filterdata data\n * @param {Promise} pendingPromise pending promise\n */\n const applyFilter = (filterdata, pendingPromise) => {\n // Reload the questions based on the specified filters. If no filters are provided,\n // use the default category filter condition.\n if (filterdata) {\n // Main join types.\n viewData.jointype = parseInt(filterSet.dataset.filterverb, 10);\n delete filterdata.jointype;\n // Retrieve filter info.\n viewData.filter = filterdata;\n if (Object.keys(filterdata).length !== 0) {\n if (!isNaN(viewData.jointype)) {\n filterdata.jointype = viewData.jointype;\n }\n updateUrlParams(filterdata);\n }\n }\n // Load questions for first page.\n viewData.filter = JSON.stringify(filterdata);\n viewData.sortdata = JSON.stringify(sortData);\n Fragment.loadFragment(component, callback, contextId, viewData)\n // Render questions for first page and pagination.\n .then((questionhtml, jsfooter) => {\n const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);\n if (questionhtml === undefined) {\n questionhtml = '';\n }\n if (jsfooter === undefined) {\n jsfooter = '';\n }\n Templates.replaceNode(questionscontainer, questionhtml, jsfooter);\n // Resolve filter promise.\n if (pendingPromise) {\n pendingPromise.resolve();\n }\n return {questionhtml, jsfooter};\n })\n .catch(Notification.exception);\n };\n\n // Init core filter processor with apply callback.\n const coreFilter = new CoreFilter(filterSet, applyFilter);\n coreFilter.activeFilters = {}; // Unset useless courseid filter.\n coreFilter.init();\n\n /**\n * Update URL Param based upon the current filter.\n *\n * @param {Object} filters Active filters.\n */\n const updateUrlParams = (filters) => {\n const url = new URL(location.href);\n const filterQuery = JSON.stringify(filters);\n url.searchParams.set('filter', filterQuery);\n history.pushState(filters, '', url);\n const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);\n if (editSwitch) {\n const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);\n const editSwitchUrl = new URL(editSwitchUrlInput.value);\n editSwitchUrl.searchParams.set('filter', filterQuery);\n editSwitchUrlInput.value = editSwitchUrl;\n editSwitch.dataset.pageurl = editSwitchUrl;\n }\n };\n\n /**\n * Cleans URL parameters.\n */\n const cleanUrlParams = () => {\n const queryString = location.search;\n const urlParams = new URLSearchParams(queryString);\n if (urlParams.has('cmid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('cmid', urlParams.get('cmid'));\n history.pushState({}, '', cleanedUrl);\n }\n\n if (urlParams.has('courseid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('courseid', urlParams.get('courseid'));\n history.pushState({}, '', cleanedUrl);\n }\n };\n\n // Add listeners for the sorting, paging and clear actions.\n document.addEventListener('click', e => {\n const sortableLink = e.target.closest(SELECTORS.SORT_LINK);\n const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);\n const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);\n if (sortableLink) {\n e.preventDefault();\n const oldSort = sortData;\n sortData = {};\n sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;\n for (const sortname in oldSort) {\n if (sortname !== sortableLink.dataset.sortname) {\n sortData[sortname] = oldSort[sortname];\n }\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter();\n }\n if (paginationLink) {\n e.preventDefault();\n const paginationURL = new URL(paginationLink.getAttribute(\"href\"));\n const qpage = paginationURL.searchParams.get('qpage');\n if (paginationURL.search !== null) {\n viewData.qpage = qpage;\n coreFilter.updateTableFromFilter();\n }\n }\n if (clearLink) {\n cleanUrlParams();\n }\n });\n\n // Run apply filter at page load.\n pagevars = JSON.parse(pagevars);\n let initialFilters;\n let jointype = null;\n if (pagevars.filter) {\n // Load initial filter based on page vars.\n initialFilters = pagevars.filter;\n if (pagevars.jointype) {\n jointype = pagevars.jointype;\n }\n }\n\n if (Object.entries(initialFilters).length !== 0) {\n // Remove the default empty filter row.\n const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow);\n if (emptyFilterRow) {\n emptyFilterRow.remove();\n }\n\n // Add filters.\n let rowcount = 0;\n for (const urlFilter in initialFilters) {\n if (urlFilter === 'jointype') {\n jointype = initialFilters[urlFilter];\n continue;\n }\n // Add each filter row.\n rowcount += 1;\n const filterdata = {\n filtertype: urlFilter,\n values: initialFilters[urlFilter].values,\n jointype: initialFilters[urlFilter].jointype,\n filteroptions: initialFilters[urlFilter].filteroptions,\n rownum: rowcount\n };\n coreFilter.addFilterRow(filterdata);\n }\n coreFilter.filterSet.dataset.filterverb = jointype;\n\n // Since we must filter by category, it does not make sense to allow the top-level \"match any\" or \"match none\" conditions,\n // as this would exclude the category. Remove those options and disable the select.\n const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join);\n join.querySelectorAll(`option:not([value=\"${jointype}\"])`).forEach((option) => option.remove());\n join.disabled = true;\n }\n};\n"],"names":["filterRegionId","defaultcourseid","defaultcategoryid","perpage","contextId","component","callback","view","cmid","pagevars","extraparams","SELECTORS","filterSet","document","querySelector","viewData","cat","courseid","filter","jointype","qpage","qperpage","sortdata","lastchanged","_document$querySelect2","value","sortData","defaultSort","_document$querySelect3","dataset","_document$querySelect4","defaultsort","JSON","parse","coreFilter","CoreFilter","filterdata","pendingPromise","parseInt","filterverb","Object","keys","length","isNaN","updateUrlParams","stringify","loadFragment","then","questionhtml","jsfooter","questionscontainer","undefined","replaceNode","resolve","catch","Notification","exception","activeFilters","init","filters","url","URL","location","href","filterQuery","searchParams","set","history","pushState","editSwitch","editSwitchUrlInput","editSwitchUrl","pageurl","initialFilters","addEventListener","e","sortableLink","target","closest","paginationLink","clearLink","Selectors","filterset","actions","resetFilters","preventDefault","oldSort","sortname","sortorder","updateTableFromFilter","paginationURL","getAttribute","get","search","queryString","urlParams","URLSearchParams","has","cleanedUrl","replace","cleanUrlParams","entries","emptyFilterRow","regions","remove","rowcount","urlFilter","filtertype","values","filteroptions","rownum","addFilterRow","join","fields","querySelectorAll","forEach","option","disabled"],"mappings":"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"} \ No newline at end of file diff --git a/question/amd/src/filter.js b/question/amd/src/filter.js index d89a0190218..99e72cfadd7 100644 --- a/question/amd/src/filter.js +++ b/question/amd/src/filter.js @@ -70,7 +70,9 @@ export const init = ( const filterSet = document.querySelector(`#${filterRegionId}`); - const filterCondition = { + const viewData = { + view: view, + cmid: cmid, cat: defaultcategoryid, courseid: defaultcourseid, filter: {}, @@ -78,12 +80,14 @@ export const init = ( qpage: 0, qperpage: perpage, sortdata: {}, - tabname: 'questions', + extraparams: extraparams, + lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null, }; + let sortData = {}; const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort; if (defaultSort) { - filterCondition.sortData = JSON.parse(defaultSort); + sortData = JSON.parse(defaultSort); } /** @@ -97,25 +101,20 @@ export const init = ( // use the default category filter condition. if (filterdata) { // Main join types. - filterCondition.jointype = parseInt(filterSet.dataset.filterverb, 10); + viewData.jointype = parseInt(filterSet.dataset.filterverb, 10); delete filterdata.jointype; // Retrieve filter info. - filterCondition.filter = filterdata; + viewData.filter = filterdata; if (Object.keys(filterdata).length !== 0) { - if (!isNaN(filterCondition.jointype)) { - filterdata.jointype = filterCondition.jointype; + if (!isNaN(viewData.jointype)) { + filterdata.jointype = viewData.jointype; } updateUrlParams(filterdata); } } // Load questions for first page. - const viewData = { - view: view, - cmid: cmid, - filtercondition: JSON.stringify(filterCondition), - extraparams: extraparams, - lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null - }; + viewData.filter = JSON.stringify(filterdata); + viewData.sortdata = JSON.stringify(sortData); Fragment.loadFragment(component, callback, contextId, viewData) // Render questions for first page and pagination. .then((questionhtml, jsfooter) => { @@ -126,7 +125,7 @@ export const init = ( if (jsfooter === undefined) { jsfooter = ''; } - Templates.replaceNodeContents(questionscontainer, questionhtml, jsfooter); + Templates.replaceNode(questionscontainer, questionhtml, jsfooter); // Resolve filter promise. if (pendingPromise) { pendingPromise.resolve(); @@ -187,15 +186,15 @@ export const init = ( const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters); if (sortableLink) { e.preventDefault(); - const oldSort = filterCondition.sortdata; - filterCondition.sortdata = {}; - filterCondition.sortdata[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder; + const oldSort = sortData; + sortData = {}; + sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder; for (const sortname in oldSort) { if (sortname !== sortableLink.dataset.sortname) { - filterCondition.sortdata[sortname] = oldSort[sortname]; + sortData[sortname] = oldSort[sortname]; } } - filterCondition.qpage = 0; + viewData.qpage = 0; coreFilter.updateTableFromFilter(); } if (paginationLink) { @@ -203,7 +202,7 @@ export const init = ( const paginationURL = new URL(paginationLink.getAttribute("href")); const qpage = paginationURL.searchParams.get('qpage'); if (paginationURL.search !== null) { - filterCondition.qpage = qpage; + viewData.qpage = qpage; coreFilter.updateTableFromFilter(); } } diff --git a/question/bank/columnsortorder/amd/build/actions.min.js b/question/bank/columnsortorder/amd/build/actions.min.js index 495515bccca..08142abbd66 100644 --- a/question/bank/columnsortorder/amd/build/actions.min.js +++ b/question/bank/columnsortorder/amd/build/actions.min.js @@ -6,6 +6,6 @@ define("qbank_columnsortorder/actions",["exports","core/sortable_list","jquery", * @copyright 2023 onwards Catalyst IT Europe Ltd * @author Mark Johnson <mark.johnson@catalyst-eu.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupSortableLists=_exports.setupActionButtons=_exports.getColumnOrder=_exports.SELECTORS=void 0,_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery),repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(repository),_notification=_interopRequireDefault(_notification),_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates);const SELECTORS={columnList:".qbank-column-list",sortableColumn:".qbank-sortable-column",removeLink:"[data-action=remove]",moveHandler:"[data-drag-type=move]",addColumn:".addcolumn",addLink:"[data-action=add]",actionLink:".action-link"};_exports.SELECTORS=SELECTORS;_exports.setupSortableLists=function(listRoot){let vertical=arguments.length>1&&void 0!==arguments[1]&&arguments[1],global=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const sortableList=new _sortable_list.default(listRoot,{moveHandlerSelector:SELECTORS.moveHandler,isHorizontal:!vertical});sortableList.getElementName=element=>Promise.resolve(element.data("name"));const sortableColumns=(0,_jquery.default)(SELECTORS.sortableColumn);return sortableColumns.on(_sortable_list.default.EVENTS.DROP,(()=>{repository.setColumnbankOrder(getColumnOrder(listRoot),global).catch(_notification.default.exception),listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach((item=>item.classList.remove("active")))})),sortableColumns.on(_sortable_list.default.EVENTS.DRAGSTART,(event=>{event.currentTarget.classList.add("active")})),sortableColumns};_exports.setupActionButtons=function(uiRoot){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];uiRoot.addEventListener("click",(async e=>{const actionLink=e.target.closest(SELECTORS.actionLink);if(actionLink)try{e.preventDefault();const action=actionLink.dataset.action;if("add"===action||"remove"===action){const hiddenColumns=[],addColumnList=document.querySelector(SELECTORS.addColumn);addColumnList&&addColumnList.querySelectorAll(SELECTORS.addLink).forEach((item=>{"add"===action&&item===actionLink||hiddenColumns.push(item.dataset.column)})),"remove"===action&&hiddenColumns.push(actionLink.dataset.column),await repository.setHiddenColumns(hiddenColumns,global)}else"reset"===action&&await Promise.all([repository.setColumnbankOrder([],global),repository.setHiddenColumns([],global),repository.setColumnSize("",global)]);const fragmentData=uiRoot.dataset;_fragment.default.loadFragment(fragmentData.component,fragmentData.callback,fragmentData.contextid).then(((html,js)=>_templates.default.replaceNode(uiRoot,html,js))).catch(_notification.default.exception)}catch(ex){await _notification.default.exception(ex)}}))};const getColumnOrder=listRoot=>{const columns=Array.from(listRoot.querySelectorAll("[data-columnid]")).map((column=>column.dataset.columnid));return columns.filter(((value,index)=>columns.indexOf(value)===index))};_exports.getColumnOrder=getColumnOrder})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupSortableLists=_exports.setupActionButtons=_exports.getColumnOrder=_exports.SELECTORS=void 0,_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery),repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(repository),_notification=_interopRequireDefault(_notification),_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates);const SELECTORS={columnList:".qbank-column-list",sortableColumn:".qbank-sortable-column",removeLink:"[data-action=remove]",moveHandler:"[data-drag-type=move]",addColumn:".addcolumn",addLink:"[data-action=add]",actionLink:".action-link"};_exports.SELECTORS=SELECTORS;_exports.setupSortableLists=function(listRoot){let vertical=arguments.length>1&&void 0!==arguments[1]&&arguments[1],global=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const sortableList=new _sortable_list.default(listRoot,{moveHandlerSelector:SELECTORS.moveHandler,isHorizontal:!vertical});sortableList.getElementName=element=>Promise.resolve(element.data("name"));const sortableColumns=(0,_jquery.default)(SELECTORS.sortableColumn);return sortableColumns.on(_sortable_list.default.EVENTS.DROP,(()=>{repository.setColumnbankOrder(getColumnOrder(listRoot),global).catch(_notification.default.exception),listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach((item=>item.classList.remove("active")))})),sortableColumns.on(_sortable_list.default.EVENTS.DRAGSTART,(event=>{event.currentTarget.classList.add("active")})),sortableColumns};_exports.setupActionButtons=function(uiRoot){let global=arguments.length>1&&void 0!==arguments[1]&&arguments[1];uiRoot.addEventListener("click",(async e=>{const actionLink=e.target.closest(SELECTORS.actionLink);if(actionLink)try{e.preventDefault();const action=actionLink.dataset.action;if("add"===action||"remove"===action){const hiddenColumns=[],addColumnList=document.querySelector(SELECTORS.addColumn);addColumnList&&addColumnList.querySelectorAll(SELECTORS.addLink).forEach((item=>{"add"===action&&item===actionLink||hiddenColumns.push(item.dataset.column)})),"remove"===action&&hiddenColumns.push(actionLink.dataset.column),await repository.setHiddenColumns(hiddenColumns,global)}else"reset"===action&&await Promise.all([repository.setColumnbankOrder([],global),repository.setHiddenColumns([],global),repository.setColumnSize("",global)]);const fragmentData=uiRoot.dataset,actionUrl=new URL(actionLink.href),returnUrl=new URL(actionUrl.searchParams.get("returnurl").replaceAll("&","&")),viewData={},sortData={};returnUrl&&returnUrl.searchParams.forEach(((value,key)=>{const sortItem=key.match(/sortdata\[([^\]]+)\]/);sortItem?sortData[sortItem.pop()]=value:viewData[key]=value})),viewData.sortdata=JSON.stringify(sortData),_fragment.default.loadFragment(fragmentData.component,fragmentData.callback,fragmentData.contextid,viewData).then(((html,js)=>_templates.default.replaceNode(uiRoot,html,js))).catch(_notification.default.exception)}catch(ex){await _notification.default.exception(ex)}}))};const getColumnOrder=listRoot=>{const columns=Array.from(listRoot.querySelectorAll("[data-columnid]")).map((column=>column.dataset.columnid));return columns.filter(((value,index)=>columns.indexOf(value)===index))};_exports.getColumnOrder=getColumnOrder})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/question/bank/columnsortorder/amd/build/actions.min.js.map b/question/bank/columnsortorder/amd/build/actions.min.js.map index dccff042564..9c61b52c8de 100644 --- a/question/bank/columnsortorder/amd/build/actions.min.js.map +++ b/question/bank/columnsortorder/amd/build/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../src/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 * Common javascript for handling actions on the admin page and the user's view of the question bank.\n *\n * @module qbank_columnsortorder/actions\n * @copyright 2023 onwards Catalyst IT Europe Ltd\n * @author Mark Johnson <mark.johnson@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SortableList from 'core/sortable_list';\nimport $ from 'jquery';\nimport * as repository from 'qbank_columnsortorder/repository';\nimport Notification from \"core/notification\";\nimport Fragment from \"core/fragment\";\nimport Templates from \"core/templates\";\n\nexport const SELECTORS = {\n columnList: '.qbank-column-list',\n sortableColumn: '.qbank-sortable-column',\n removeLink: '[data-action=remove]',\n moveHandler: '[data-drag-type=move]',\n addColumn: '.addcolumn',\n addLink: '[data-action=add]',\n actionLink: '.action-link',\n};\n\n/**\n * Sets up sortable list in the column sort order page.\n *\n * @param {Element} listRoot Element containing the sortable list.\n * @param {Boolean} vertical Is the list in vertical orientation, rather than horizonal?\n * @param {Boolean} global Should changes be saved to global config, rather than user preferences?\n * @return {jQuery} sortable column elements, for attaching additional event listeners.\n */\nexport const setupSortableLists = (listRoot, vertical = false, global = false) => {\n const sortableList = new SortableList(listRoot, {\n moveHandlerSelector: SELECTORS.moveHandler,\n isHorizontal: !vertical,\n });\n sortableList.getElementName = element => Promise.resolve(element.data('name'));\n\n const sortableColumns = $(SELECTORS.sortableColumn);\n\n sortableColumns.on(SortableList.EVENTS.DROP, () => {\n repository.setColumnbankOrder(getColumnOrder(listRoot), global).catch(Notification.exception);\n listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach(item => item.classList.remove('active'));\n });\n\n sortableColumns.on(SortableList.EVENTS.DRAGSTART, (event) => {\n event.currentTarget.classList.add('active');\n });\n\n return sortableColumns;\n};\n\n/**\n * Set up event handlers for action buttons.\n *\n * For each action, call the web service to update the appropriate setting or user preference, then call the fragment to\n * refresh the view.\n *\n * @param {Element} uiRoot The root of the question bank UI.\n * @param {Boolean} global Should changes be saved to global config, rather than user preferences?\n */\nexport const setupActionButtons = (uiRoot, global = false) => {\n uiRoot.addEventListener('click', async(e) => {\n const actionLink = e.target.closest(SELECTORS.actionLink);\n if (!actionLink) {\n return;\n }\n try {\n e.preventDefault();\n const action = actionLink.dataset.action;\n if (action === 'add' || action === 'remove') {\n const hiddenColumns = [];\n const addColumnList = document.querySelector(SELECTORS.addColumn);\n if (addColumnList) {\n addColumnList.querySelectorAll(SELECTORS.addLink).forEach(item => {\n if (action === 'add' && item === actionLink) {\n return;\n }\n hiddenColumns.push(item.dataset.column);\n });\n }\n if (action === 'remove') {\n hiddenColumns.push(actionLink.dataset.column);\n }\n await repository.setHiddenColumns(hiddenColumns, global);\n } else if (action === 'reset') {\n await Promise.all([\n repository.setColumnbankOrder([], global),\n repository.setHiddenColumns([], global),\n repository.setColumnSize('', global),\n ]);\n }\n const fragmentData = uiRoot.dataset;\n // We have to use then() there, as loadFragment doesn't appear to work with await.\n Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid)\n .then((html, js) => {\n return Templates.replaceNode(uiRoot, html, js);\n })\n .catch(Notification.exception);\n } catch (ex) {\n await Notification.exception(ex);\n }\n });\n};\n\n/**\n * Gets the newly reordered columns to display in the question bank view.\n * @param {Element} listRoot\n * @returns {Array}\n */\nexport const getColumnOrder = listRoot => {\n const columns = Array.from(listRoot.querySelectorAll('[data-columnid]'))\n .map(column => column.dataset.columnid);\n\n return columns.filter((value, index) => columns.indexOf(value) === index);\n};\n"],"names":["SELECTORS","columnList","sortableColumn","removeLink","moveHandler","addColumn","addLink","actionLink","listRoot","vertical","global","sortableList","SortableList","moveHandlerSelector","isHorizontal","getElementName","element","Promise","resolve","data","sortableColumns","on","EVENTS","DROP","repository","setColumnbankOrder","getColumnOrder","catch","Notification","exception","querySelectorAll","forEach","item","classList","remove","DRAGSTART","event","currentTarget","add","uiRoot","addEventListener","async","e","target","closest","preventDefault","action","dataset","hiddenColumns","addColumnList","document","querySelector","push","column","setHiddenColumns","all","setColumnSize","fragmentData","loadFragment","component","callback","contextid","then","html","js","Templates","replaceNode","ex","columns","Array","from","map","columnid","filter","value","index","indexOf"],"mappings":";;;;;;;;6jCA+BaA,UAAY,CACrBC,WAAY,qBACZC,eAAgB,yBAChBC,WAAY,uBACZC,YAAa,wBACbC,UAAW,aACXC,QAAS,oBACTC,WAAY,yEAWkB,SAACC,cAAUC,iEAAkBC,qEACrDC,aAAe,IAAIC,uBAAaJ,SAAU,CAC5CK,oBAAqBb,UAAUI,YAC/BU,cAAeL,WAEnBE,aAAaI,eAAiBC,SAAWC,QAAQC,QAAQF,QAAQG,KAAK,eAEhEC,iBAAkB,mBAAEpB,UAAUE,uBAEpCkB,gBAAgBC,GAAGT,uBAAaU,OAAOC,MAAM,KACzCC,WAAWC,mBAAmBC,eAAelB,UAAWE,QAAQiB,MAAMC,sBAAaC,WACnFrB,SAASsB,iBAAiB9B,UAAUE,gBAAgB6B,SAAQC,MAAQA,KAAKC,UAAUC,OAAO,eAG9Fd,gBAAgBC,GAAGT,uBAAaU,OAAOa,WAAYC,QAC/CA,MAAMC,cAAcJ,UAAUK,IAAI,aAG/BlB,6CAYuB,SAACmB,YAAQ7B,+DACvC6B,OAAOC,iBAAiB,SAASC,MAAAA,UACvBlC,WAAamC,EAAEC,OAAOC,QAAQ5C,UAAUO,eACzCA,eAIDmC,EAAEG,uBACIC,OAASvC,WAAWwC,QAAQD,UACnB,QAAXA,QAA+B,WAAXA,OAAqB,OACnCE,cAAgB,GAChBC,cAAgBC,SAASC,cAAcnD,UAAUK,WACnD4C,eACAA,cAAcnB,iBAAiB9B,UAAUM,SAASyB,SAAQC,OACvC,QAAXc,QAAoBd,OAASzB,YAGjCyC,cAAcI,KAAKpB,KAAKe,QAAQM,WAGzB,WAAXP,QACAE,cAAcI,KAAK7C,WAAWwC,QAAQM,cAEpC7B,WAAW8B,iBAAiBN,cAAetC,YAC/B,UAAXoC,cACD7B,QAAQsC,IAAI,CACd/B,WAAWC,mBAAmB,GAAIf,QAClCc,WAAW8B,iBAAiB,GAAI5C,QAChCc,WAAWgC,cAAc,GAAI9C,gBAG/B+C,aAAelB,OAAOQ,0BAEnBW,aAAaD,aAAaE,UAAWF,aAAaG,SAAUH,aAAaI,WAC7EC,MAAK,CAACC,KAAMC,KACFC,mBAAUC,YAAY3B,OAAQwB,KAAMC,MAE9CrC,MAAMC,sBAAaC,WAC1B,MAAOsC,UACCvC,sBAAaC,UAAUsC,eAU5BzC,eAAiBlB,iBACpB4D,QAAUC,MAAMC,KAAK9D,SAASsB,iBAAiB,oBAChDyC,KAAIlB,QAAUA,OAAON,QAAQyB,kBAE3BJ,QAAQK,QAAO,CAACC,MAAOC,QAAUP,QAAQQ,QAAQF,SAAWC"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../src/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 * Common javascript for handling actions on the admin page and the user's view of the question bank.\n *\n * @module qbank_columnsortorder/actions\n * @copyright 2023 onwards Catalyst IT Europe Ltd\n * @author Mark Johnson <mark.johnson@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SortableList from 'core/sortable_list';\nimport $ from 'jquery';\nimport * as repository from 'qbank_columnsortorder/repository';\nimport Notification from \"core/notification\";\nimport Fragment from \"core/fragment\";\nimport Templates from \"core/templates\";\n\nexport const SELECTORS = {\n columnList: '.qbank-column-list',\n sortableColumn: '.qbank-sortable-column',\n removeLink: '[data-action=remove]',\n moveHandler: '[data-drag-type=move]',\n addColumn: '.addcolumn',\n addLink: '[data-action=add]',\n actionLink: '.action-link',\n};\n\n/**\n * Sets up sortable list in the column sort order page.\n *\n * @param {Element} listRoot Element containing the sortable list.\n * @param {Boolean} vertical Is the list in vertical orientation, rather than horizonal?\n * @param {Boolean} global Should changes be saved to global config, rather than user preferences?\n * @return {jQuery} sortable column elements, for attaching additional event listeners.\n */\nexport const setupSortableLists = (listRoot, vertical = false, global = false) => {\n const sortableList = new SortableList(listRoot, {\n moveHandlerSelector: SELECTORS.moveHandler,\n isHorizontal: !vertical,\n });\n sortableList.getElementName = element => Promise.resolve(element.data('name'));\n\n const sortableColumns = $(SELECTORS.sortableColumn);\n\n sortableColumns.on(SortableList.EVENTS.DROP, () => {\n repository.setColumnbankOrder(getColumnOrder(listRoot), global).catch(Notification.exception);\n listRoot.querySelectorAll(SELECTORS.sortableColumn).forEach(item => item.classList.remove('active'));\n });\n\n sortableColumns.on(SortableList.EVENTS.DRAGSTART, (event) => {\n event.currentTarget.classList.add('active');\n });\n\n return sortableColumns;\n};\n\n/**\n * Set up event handlers for action buttons.\n *\n * For each action, call the web service to update the appropriate setting or user preference, then call the fragment to\n * refresh the view.\n *\n * @param {Element} uiRoot The root of the question bank UI.\n * @param {Boolean} global Should changes be saved to global config, rather than user preferences?\n */\nexport const setupActionButtons = (uiRoot, global = false) => {\n uiRoot.addEventListener('click', async(e) => {\n const actionLink = e.target.closest(SELECTORS.actionLink);\n if (!actionLink) {\n return;\n }\n try {\n e.preventDefault();\n const action = actionLink.dataset.action;\n if (action === 'add' || action === 'remove') {\n const hiddenColumns = [];\n const addColumnList = document.querySelector(SELECTORS.addColumn);\n if (addColumnList) {\n addColumnList.querySelectorAll(SELECTORS.addLink).forEach(item => {\n if (action === 'add' && item === actionLink) {\n return;\n }\n hiddenColumns.push(item.dataset.column);\n });\n }\n if (action === 'remove') {\n hiddenColumns.push(actionLink.dataset.column);\n }\n await repository.setHiddenColumns(hiddenColumns, global);\n } else if (action === 'reset') {\n await Promise.all([\n repository.setColumnbankOrder([], global),\n repository.setHiddenColumns([], global),\n repository.setColumnSize('', global),\n ]);\n }\n const fragmentData = uiRoot.dataset;\n const actionUrl = new URL(actionLink.href);\n const returnUrl = new URL(actionUrl.searchParams.get('returnurl').replaceAll('&', '&'));\n const viewData = {};\n const sortData = {};\n if (returnUrl) {\n returnUrl.searchParams.forEach((value, key) => {\n // Match keys like 'sortdata[fieldname]' and convert them to an array,\n // because the fragment API doesn't like non-alphanum argument keys.\n const sortItem = key.match(/sortdata\\[([^\\]]+)\\]/);\n if (sortItem) {\n // The item returned by sortItem.pop() is the contents of the matching group, the field name.\n sortData[sortItem.pop()] = value;\n } else {\n viewData[key] = value;\n }\n });\n }\n viewData.sortdata = JSON.stringify(sortData);\n // We have to use then() there, as loadFragment doesn't appear to work with await.\n Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)\n .then((html, js) => {\n return Templates.replaceNode(uiRoot, html, js);\n })\n .catch(Notification.exception);\n } catch (ex) {\n await Notification.exception(ex);\n }\n });\n};\n\n/**\n * Gets the newly reordered columns to display in the question bank view.\n * @param {Element} listRoot\n * @returns {Array}\n */\nexport const getColumnOrder = listRoot => {\n const columns = Array.from(listRoot.querySelectorAll('[data-columnid]'))\n .map(column => column.dataset.columnid);\n\n return columns.filter((value, index) => columns.indexOf(value) === index);\n};\n"],"names":["SELECTORS","columnList","sortableColumn","removeLink","moveHandler","addColumn","addLink","actionLink","listRoot","vertical","global","sortableList","SortableList","moveHandlerSelector","isHorizontal","getElementName","element","Promise","resolve","data","sortableColumns","on","EVENTS","DROP","repository","setColumnbankOrder","getColumnOrder","catch","Notification","exception","querySelectorAll","forEach","item","classList","remove","DRAGSTART","event","currentTarget","add","uiRoot","addEventListener","async","e","target","closest","preventDefault","action","dataset","hiddenColumns","addColumnList","document","querySelector","push","column","setHiddenColumns","all","setColumnSize","fragmentData","actionUrl","URL","href","returnUrl","searchParams","get","replaceAll","viewData","sortData","value","key","sortItem","match","pop","sortdata","JSON","stringify","loadFragment","component","callback","contextid","then","html","js","Templates","replaceNode","ex","columns","Array","from","map","columnid","filter","index","indexOf"],"mappings":";;;;;;;;6jCA+BaA,UAAY,CACrBC,WAAY,qBACZC,eAAgB,yBAChBC,WAAY,uBACZC,YAAa,wBACbC,UAAW,aACXC,QAAS,oBACTC,WAAY,yEAWkB,SAACC,cAAUC,iEAAkBC,qEACrDC,aAAe,IAAIC,uBAAaJ,SAAU,CAC5CK,oBAAqBb,UAAUI,YAC/BU,cAAeL,WAEnBE,aAAaI,eAAiBC,SAAWC,QAAQC,QAAQF,QAAQG,KAAK,eAEhEC,iBAAkB,mBAAEpB,UAAUE,uBAEpCkB,gBAAgBC,GAAGT,uBAAaU,OAAOC,MAAM,KACzCC,WAAWC,mBAAmBC,eAAelB,UAAWE,QAAQiB,MAAMC,sBAAaC,WACnFrB,SAASsB,iBAAiB9B,UAAUE,gBAAgB6B,SAAQC,MAAQA,KAAKC,UAAUC,OAAO,eAG9Fd,gBAAgBC,GAAGT,uBAAaU,OAAOa,WAAYC,QAC/CA,MAAMC,cAAcJ,UAAUK,IAAI,aAG/BlB,6CAYuB,SAACmB,YAAQ7B,+DACvC6B,OAAOC,iBAAiB,SAASC,MAAAA,UACvBlC,WAAamC,EAAEC,OAAOC,QAAQ5C,UAAUO,eACzCA,eAIDmC,EAAEG,uBACIC,OAASvC,WAAWwC,QAAQD,UACnB,QAAXA,QAA+B,WAAXA,OAAqB,OACnCE,cAAgB,GAChBC,cAAgBC,SAASC,cAAcnD,UAAUK,WACnD4C,eACAA,cAAcnB,iBAAiB9B,UAAUM,SAASyB,SAAQC,OACvC,QAAXc,QAAoBd,OAASzB,YAGjCyC,cAAcI,KAAKpB,KAAKe,QAAQM,WAGzB,WAAXP,QACAE,cAAcI,KAAK7C,WAAWwC,QAAQM,cAEpC7B,WAAW8B,iBAAiBN,cAAetC,YAC/B,UAAXoC,cACD7B,QAAQsC,IAAI,CACd/B,WAAWC,mBAAmB,GAAIf,QAClCc,WAAW8B,iBAAiB,GAAI5C,QAChCc,WAAWgC,cAAc,GAAI9C,gBAG/B+C,aAAelB,OAAOQ,QACtBW,UAAY,IAAIC,IAAIpD,WAAWqD,MAC/BC,UAAY,IAAIF,IAAID,UAAUI,aAAaC,IAAI,aAAaC,WAAW,QAAS,MAChFC,SAAW,GACXC,SAAW,GACbL,WACAA,UAAUC,aAAa/B,SAAQ,CAACoC,MAAOC,aAG7BC,SAAWD,IAAIE,MAAM,wBACvBD,SAEAH,SAASG,SAASE,OAASJ,MAE3BF,SAASG,KAAOD,SAI5BF,SAASO,SAAWC,KAAKC,UAAUR,4BAE1BS,aAAalB,aAAamB,UAAWnB,aAAaoB,SAAUpB,aAAaqB,UAAWb,UACxFc,MAAK,CAACC,KAAMC,KACFC,mBAAUC,YAAY5C,OAAQyC,KAAMC,MAE9CtD,MAAMC,sBAAaC,WAC1B,MAAOuD,UACCxD,sBAAaC,UAAUuD,eAU5B1D,eAAiBlB,iBACpB6E,QAAUC,MAAMC,KAAK/E,SAASsB,iBAAiB,oBAChD0D,KAAInC,QAAUA,OAAON,QAAQ0C,kBAE3BJ,QAAQK,QAAO,CAACvB,MAAOwB,QAAUN,QAAQO,QAAQzB,SAAWwB"} \ No newline at end of file diff --git a/question/bank/columnsortorder/amd/build/user_actions.min.js b/question/bank/columnsortorder/amd/build/user_actions.min.js index 16f3f9ee4ef..11a3626de08 100644 --- a/question/bank/columnsortorder/amd/build/user_actions.min.js +++ b/question/bank/columnsortorder/amd/build/user_actions.min.js @@ -6,6 +6,6 @@ define("qbank_columnsortorder/user_actions",["exports","qbank_columnsortorder/ac * @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 - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,actions=_interopRequireWildcard(actions),repository=_interopRequireWildcard(repository),_modal_events=_interopRequireDefault(_modal_events),_modal_factory=_interopRequireDefault(_modal_factory),_notification=_interopRequireDefault(_notification),_sortable_list=_interopRequireDefault(_sortable_list),_templates=_interopRequireDefault(_templates);const SELECTORS_uiRoot=".questionbankwindow",SELECTORS_moveAction=".menu-action[data-action=move]",SELECTORS_resizeAction=".menu-action[data-action=resize]",SELECTORS_resizeHandle=".qbank_columnsortorder-action-handle.resize",SELECTORS_handleContainer=".handle-container",SELECTORS_headerContainer=".header-container",SELECTORS_tableColumn=identifier=>'td[data-columnid="'.concat(identifier.replace(/["\\]/g,"\\$&"),'"]');let currentHeader,currentX,suspendShowHideHandles=!1;const serialiseColumnSizes=uiRoot=>{const columnSizes=[];return uiRoot.querySelectorAll("th").forEach((header=>{const width=parseInt(header.style.width);width&&!isNaN(width)&&columnSizes.push({column:header.dataset.columnid,width:width})})),JSON.stringify(columnSizes)},showResizeModal=async(currentHeader,uiRoot)=>{const initialWidth=currentHeader.offsetWidth,modal=await _modal_factory.default.create({title:(0,_str.get_string)("resizecolumn","qbank_columnsortorder",currentHeader.textContent),type:_modal_factory.default.types.SAVE_CANCEL,body:_templates.default.render("qbank_columnsortorder/resize_modal",{})}),root=modal.getRoot();root.on(_modal_events.default.cancel,(()=>{currentHeader.style.width=initialWidth+"px"})),root.on(_modal_events.default.save,(()=>{repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(_notification.default.exception)})),modal.show();const input=(await modal.bodyPromise).get(0).querySelector("input");input.value=initialWidth,input.addEventListener("change",(e=>{const newWidth=e.target.value;currentHeader.style.width=newWidth+"px"}))},reorderColumns=event=>{const header=event.target,insertAfter=header.previousElementSibling;document.querySelector(SELECTORS_uiRoot).querySelectorAll(SELECTORS_tableColumn(header.dataset.columnid)).forEach((column=>{const row=column.parentElement;if(insertAfter){row.querySelector(SELECTORS_tableColumn(insertAfter.dataset.columnid)).after(column)}else row.insertBefore(column,row.firstChild)}))};_exports.init=async()=>{const uiRoot=document.querySelector(".questionbankwindow");await(uiRoot=>new Promise((resolve=>{const headerContainers=uiRoot.querySelectorAll(SELECTORS_headerContainer);_templates.default.renderForPromise("qbank_columnsortorder/handle_container",{}).then((_ref=>{let{html:html,js:js}=_ref;return headerContainers.forEach((container=>{_templates.default.prependNodeContents(container,html,js)})),resolve(),headerContainers})).catch(_notification.default.exception)})))(uiRoot),uiRoot.querySelectorAll(SELECTORS_moveAction).forEach((moveAction=>{const header=moveAction.closest("th");header.classList.add("qbank-sortable-column");const handleContainer=header.querySelector(SELECTORS_handleContainer),context={action:"move",dragtype:"move",target:"",title:moveAction.title,pixicon:"i/dragdrop",pixcomponent:"core",popup:!0};return _templates.default.renderForPromise("qbank_columnsortorder/action_handle",context).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.prependNodeContents(handleContainer,html,js),handleContainer})).catch(_notification.default.exception)})),(uiRoot=>{uiRoot.querySelectorAll(SELECTORS_resizeAction).forEach((resizeAction=>{const handleContainer=resizeAction.closest(SELECTORS_headerContainer).querySelector(SELECTORS_handleContainer),context={action:"resize",target:"",title:resizeAction.title,pixicon:"i/twoway",pixcomponent:"core",popup:!0};return _templates.default.renderForPromise("qbank_columnsortorder/action_handle",context).then((_ref3=>{let{html:html,js:js}=_ref3;return _templates.default.appendNodeContents(handleContainer,html,js),handleContainer})).catch(_notification.default.exception)}));let moveTracker=!1,currentResizeHandle=null;uiRoot.addEventListener("mousedown",(e=>{currentResizeHandle=e.target.closest(SELECTORS_resizeHandle),currentResizeHandle&&(currentX=e.pageX,currentHeader=e.target.closest(actions.SELECTORS.sortableColumn),moveTracker=!1,suspendShowHideHandles=!0)})),document.addEventListener("mousemove",(e=>{if(!currentHeader||!currentResizeHandle||0===currentX)return;document.getSelection().removeAllRanges();const offset=e.pageX-currentX;currentX=e.pageX;const newWidth=currentHeader.offsetWidth+offset;currentHeader.style.width=newWidth+"px",moveTracker=!0})),document.addEventListener("mouseup",(()=>{currentHeader&¤tResizeHandle&&0!==currentX&&(moveTracker?repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(_notification.default.exception):showResizeModal(currentHeader,uiRoot),currentHeader=null,currentResizeHandle=null,currentX=0,moveTracker=!1,suspendShowHideHandles=!1)}))})(uiRoot),(uiRoot=>{let shownHeader=null,tableHead=uiRoot.querySelector("thead");uiRoot.addEventListener("mouseover",(e=>{if(suspendShowHideHandles)return;const header=e.target.closest(actions.SELECTORS.sortableColumn);var _tableHead$querySelec;(header||shownHeader)&&(header&&header===shownHeader||(null===(_tableHead$querySelec=tableHead.querySelector(".show-handles"))||void 0===_tableHead$querySelec||_tableHead$querySelec.classList.remove("show-handles"),shownHeader=header,header&&header.classList.add("show-handles")))}))})(uiRoot);const sortableColumns=actions.setupSortableLists(uiRoot.querySelector(actions.SELECTORS.columnList));sortableColumns.on(_sortable_list.default.EVENTS.DROP,reorderColumns),sortableColumns.on(_sortable_list.default.EVENTS.DRAGSTART,(()=>{suspendShowHideHandles=!0})),sortableColumns.on(_sortable_list.default.EVENTS.DRAGEND,(()=>{suspendShowHideHandles=!1})),(uiRoot=>{uiRoot.addEventListener("click",(e=>{const moveAction=e.target.closest(SELECTORS_moveAction);moveAction&&(e.preventDefault(),moveAction.closest(actions.SELECTORS.sortableColumn).querySelector(actions.SELECTORS.moveHandler).click())}))})(uiRoot),(uiRoot=>{uiRoot.addEventListener("click",(e=>{const resizeAction=e.target.closest(SELECTORS_resizeAction);if(resizeAction){e.preventDefault();const currentHeader=resizeAction.closest("th");showResizeModal(currentHeader,uiRoot)}}))})(uiRoot)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,actions=_interopRequireWildcard(actions),repository=_interopRequireWildcard(repository),_modal_events=_interopRequireDefault(_modal_events),_modal_factory=_interopRequireDefault(_modal_factory),_notification=_interopRequireDefault(_notification),_sortable_list=_interopRequireDefault(_sortable_list),_templates=_interopRequireDefault(_templates);const SELECTORS_uiRoot=".questionbankwindow",SELECTORS_moveAction=".menu-action[data-action=move]",SELECTORS_resizeAction=".menu-action[data-action=resize]",SELECTORS_resizeHandle=".qbank_columnsortorder-action-handle.resize",SELECTORS_handleContainer=".handle-container",SELECTORS_headerContainer=".header-container",SELECTORS_tableColumn=identifier=>'td[data-columnid="'.concat(identifier.replace(/["\\]/g,"\\$&"),'"]');let currentHeader,currentX,suspendShowHideHandles=!1;const serialiseColumnSizes=uiRoot=>{const columnSizes=[];return uiRoot.querySelectorAll("th").forEach((header=>{const width=parseInt(header.style.width);width&&!isNaN(width)&&columnSizes.push({column:header.dataset.columnid,width:width})})),JSON.stringify(columnSizes)},showResizeModal=async(currentHeader,uiRoot)=>{const initialWidth=currentHeader.offsetWidth,modal=await _modal_factory.default.create({title:(0,_str.get_string)("resizecolumn","qbank_columnsortorder",currentHeader.textContent),type:_modal_factory.default.types.SAVE_CANCEL,body:_templates.default.render("qbank_columnsortorder/resize_modal",{})}),root=modal.getRoot();root.on(_modal_events.default.cancel,(()=>{currentHeader.style.width=initialWidth+"px"})),root.on(_modal_events.default.save,(()=>{repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(_notification.default.exception)})),modal.show();const input=(await modal.bodyPromise).get(0).querySelector("input");input.value=initialWidth,input.addEventListener("change",(e=>{const newWidth=e.target.value;currentHeader.style.width=newWidth+"px"}))},reorderColumns=event=>{const header=event.target,insertAfter=header.previousElementSibling;document.querySelector(SELECTORS_uiRoot).querySelectorAll(SELECTORS_tableColumn(header.dataset.columnid)).forEach((column=>{const row=column.parentElement;if(insertAfter){row.querySelector(SELECTORS_tableColumn(insertAfter.dataset.columnid)).after(column)}else row.insertBefore(column,row.firstChild)}))};_exports.init=async()=>{const uiRoot=document.getElementById("questionscontainer");await(uiRoot=>new Promise((resolve=>{const headerContainers=uiRoot.querySelectorAll(SELECTORS_headerContainer);_templates.default.renderForPromise("qbank_columnsortorder/handle_container",{}).then((_ref=>{let{html:html,js:js}=_ref;return headerContainers.forEach((container=>{_templates.default.prependNodeContents(container,html,js)})),resolve(),headerContainers})).catch(_notification.default.exception)})))(uiRoot),uiRoot.querySelectorAll(SELECTORS_moveAction).forEach((moveAction=>{const header=moveAction.closest("th");header.classList.add("qbank-sortable-column");const handleContainer=header.querySelector(SELECTORS_handleContainer),context={action:"move",dragtype:"move",target:"",title:moveAction.title,pixicon:"i/dragdrop",pixcomponent:"core",popup:!0};return _templates.default.renderForPromise("qbank_columnsortorder/action_handle",context).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.prependNodeContents(handleContainer,html,js),handleContainer})).catch(_notification.default.exception)})),(uiRoot=>{uiRoot.querySelectorAll(SELECTORS_resizeAction).forEach((resizeAction=>{const handleContainer=resizeAction.closest(SELECTORS_headerContainer).querySelector(SELECTORS_handleContainer),context={action:"resize",target:"",title:resizeAction.title,pixicon:"i/twoway",pixcomponent:"core",popup:!0};return _templates.default.renderForPromise("qbank_columnsortorder/action_handle",context).then((_ref3=>{let{html:html,js:js}=_ref3;return _templates.default.appendNodeContents(handleContainer,html,js),handleContainer})).catch(_notification.default.exception)}));let moveTracker=!1,currentResizeHandle=null;uiRoot.addEventListener("mousedown",(e=>{currentResizeHandle=e.target.closest(SELECTORS_resizeHandle),currentResizeHandle&&(currentX=e.pageX,currentHeader=e.target.closest(actions.SELECTORS.sortableColumn),moveTracker=!1,suspendShowHideHandles=!0)})),document.addEventListener("mousemove",(e=>{if(!currentHeader||!currentResizeHandle||0===currentX)return;document.getSelection().removeAllRanges();const offset=e.pageX-currentX;currentX=e.pageX;const newWidth=currentHeader.offsetWidth+offset;currentHeader.style.width=newWidth+"px",moveTracker=!0})),document.addEventListener("mouseup",(()=>{currentHeader&¤tResizeHandle&&0!==currentX&&(moveTracker?repository.setColumnSize(serialiseColumnSizes(uiRoot)).catch(_notification.default.exception):showResizeModal(currentHeader,uiRoot),currentHeader=null,currentResizeHandle=null,currentX=0,moveTracker=!1,suspendShowHideHandles=!1)}))})(uiRoot),(uiRoot=>{let shownHeader=null,tableHead=uiRoot.querySelector("thead");uiRoot.addEventListener("mouseover",(e=>{if(suspendShowHideHandles)return;const header=e.target.closest(actions.SELECTORS.sortableColumn);var _tableHead$querySelec;(header||shownHeader)&&(header&&header===shownHeader||(null===(_tableHead$querySelec=tableHead.querySelector(".show-handles"))||void 0===_tableHead$querySelec||_tableHead$querySelec.classList.remove("show-handles"),shownHeader=header,header&&header.classList.add("show-handles")))}))})(uiRoot);const sortableColumns=actions.setupSortableLists(uiRoot.querySelector(actions.SELECTORS.columnList));sortableColumns.on(_sortable_list.default.EVENTS.DROP,reorderColumns),sortableColumns.on(_sortable_list.default.EVENTS.DRAGSTART,(()=>{suspendShowHideHandles=!0})),sortableColumns.on(_sortable_list.default.EVENTS.DRAGEND,(()=>{suspendShowHideHandles=!1})),(uiRoot=>{uiRoot.addEventListener("click",(e=>{const moveAction=e.target.closest(SELECTORS_moveAction);moveAction&&(e.preventDefault(),moveAction.closest(actions.SELECTORS.sortableColumn).querySelector(actions.SELECTORS.moveHandler).click())}))})(uiRoot),(uiRoot=>{uiRoot.addEventListener("click",(e=>{const resizeAction=e.target.closest(SELECTORS_resizeAction);if(resizeAction){e.preventDefault();const currentHeader=resizeAction.closest("th");showResizeModal(currentHeader,uiRoot)}}))})(uiRoot),actions.setupActionButtons(uiRoot)}})); //# sourceMappingURL=user_actions.min.js.map \ No newline at end of file diff --git a/question/bank/columnsortorder/amd/build/user_actions.min.js.map b/question/bank/columnsortorder/amd/build/user_actions.min.js.map index d0f812cf50d..855d6778285 100644 --- a/question/bank/columnsortorder/amd/build/user_actions.min.js.map +++ b/question/bank/columnsortorder/amd/build/user_actions.min.js.map @@ -1 +1 @@ -{"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 ModalFactory from 'core/modal_factory';\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/**\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 * 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 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 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 currentHeader.style.width = newWidth + 'px';\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 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\n const initialWidth = currentHeader.offsetWidth;\n\n const modal = await ModalFactory.create({\n title: getString('resizecolumn', 'qbank_columnsortorder', currentHeader.textContent),\n type: ModalFactory.types.SAVE_CANCEL,\n body: Templates.render('qbank_columnsortorder/resize_modal', {})\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 modal.show();\n\n const body = await modal.bodyPromise;\n const input = body.get(0).querySelector('input');\n input.value = initialWidth;\n\n input.addEventListener('change', e => {\n const newWidth = e.target.value;\n currentHeader.style.width = newWidth + 'px';\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.querySelector('.questionbankwindow');\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};\n"],"names":["SELECTORS","identifier","replace","currentHeader","currentX","suspendShowHideHandles","serialiseColumnSizes","uiRoot","columnSizes","querySelectorAll","forEach","header","width","parseInt","style","isNaN","push","column","dataset","columnid","JSON","stringify","showResizeModal","async","initialWidth","offsetWidth","modal","ModalFactory","create","title","textContent","type","types","SAVE_CANCEL","body","Templates","render","root","getRoot","on","ModalEvents","cancel","save","repository","setColumnSize","catch","Notification","exception","show","input","bodyPromise","get","querySelector","value","addEventListener","e","newWidth","target","reorderColumns","event","insertAfter","previousElementSibling","document","row","parentElement","after","insertBefore","firstChild","Promise","resolve","headerContainers","renderForPromise","then","_ref","html","js","container","prependNodeContents","addHandleContainers","moveAction","closest","classList","add","handleContainer","context","action","dragtype","pixicon","pixcomponent","popup","_ref2","resizeAction","_ref3","appendNodeContents","moveTracker","currentResizeHandle","pageX","actions","sortableColumn","getSelection","removeAllRanges","offset","setUpResizeHandles","shownHeader","tableHead","remove","setupShowHideHandles","sortableColumns","setupSortableLists","columnList","SortableList","EVENTS","DROP","DRAGSTART","DRAGEND","preventDefault","moveHandler","click","setUpMoveActions","setUpResizeActions"],"mappings":"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"} \ No newline at end of file +{"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 ModalFactory from 'core/modal_factory';\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/**\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 * 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 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 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 currentHeader.style.width = newWidth + 'px';\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 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\n const initialWidth = currentHeader.offsetWidth;\n\n const modal = await ModalFactory.create({\n title: getString('resizecolumn', 'qbank_columnsortorder', currentHeader.textContent),\n type: ModalFactory.types.SAVE_CANCEL,\n body: Templates.render('qbank_columnsortorder/resize_modal', {})\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 modal.show();\n\n const body = await modal.bodyPromise;\n const input = body.get(0).querySelector('input');\n input.value = initialWidth;\n\n input.addEventListener('change', e => {\n const newWidth = e.target.value;\n currentHeader.style.width = newWidth + 'px';\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","suspendShowHideHandles","serialiseColumnSizes","uiRoot","columnSizes","querySelectorAll","forEach","header","width","parseInt","style","isNaN","push","column","dataset","columnid","JSON","stringify","showResizeModal","async","initialWidth","offsetWidth","modal","ModalFactory","create","title","textContent","type","types","SAVE_CANCEL","body","Templates","render","root","getRoot","on","ModalEvents","cancel","save","repository","setColumnSize","catch","Notification","exception","show","input","bodyPromise","get","querySelector","value","addEventListener","e","newWidth","target","reorderColumns","event","insertAfter","previousElementSibling","document","row","parentElement","after","insertBefore","firstChild","getElementById","Promise","resolve","headerContainers","renderForPromise","then","_ref","html","js","container","prependNodeContents","addHandleContainers","moveAction","closest","classList","add","handleContainer","context","action","dragtype","pixicon","pixcomponent","popup","_ref2","resizeAction","_ref3","appendNodeContents","moveTracker","currentResizeHandle","pageX","actions","sortableColumn","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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"} \ No newline at end of file diff --git a/question/bank/columnsortorder/amd/src/actions.js b/question/bank/columnsortorder/amd/src/actions.js index 197f3b36e5b..24f30f5ed42 100644 --- a/question/bank/columnsortorder/amd/src/actions.js +++ b/question/bank/columnsortorder/amd/src/actions.js @@ -109,8 +109,26 @@ export const setupActionButtons = (uiRoot, global = false) => { ]); } const fragmentData = uiRoot.dataset; + const actionUrl = new URL(actionLink.href); + const returnUrl = new URL(actionUrl.searchParams.get('returnurl').replaceAll('&', '&')); + const viewData = {}; + const sortData = {}; + if (returnUrl) { + returnUrl.searchParams.forEach((value, key) => { + // Match keys like 'sortdata[fieldname]' and convert them to an array, + // because the fragment API doesn't like non-alphanum argument keys. + const sortItem = key.match(/sortdata\[([^\]]+)\]/); + if (sortItem) { + // The item returned by sortItem.pop() is the contents of the matching group, the field name. + sortData[sortItem.pop()] = value; + } else { + viewData[key] = value; + } + }); + } + viewData.sortdata = JSON.stringify(sortData); // We have to use then() there, as loadFragment doesn't appear to work with await. - Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid) + Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData) .then((html, js) => { return Templates.replaceNode(uiRoot, html, js); }) diff --git a/question/bank/columnsortorder/amd/src/user_actions.js b/question/bank/columnsortorder/amd/src/user_actions.js index 3c8597f4c1d..4966cbebd62 100644 --- a/question/bank/columnsortorder/amd/src/user_actions.js +++ b/question/bank/columnsortorder/amd/src/user_actions.js @@ -349,7 +349,7 @@ const reorderColumns = event => { * column sorting, then enable the move and resize modals to be triggered from menu actions. */ export const init = async() => { - const uiRoot = document.querySelector('.questionbankwindow'); + const uiRoot = document.getElementById('questionscontainer'); await addHandleContainers(uiRoot); setUpMoveHandles(uiRoot.querySelectorAll(SELECTORS.moveAction)); setUpResizeHandles(uiRoot); @@ -364,4 +364,5 @@ export const init = async() => { }); setUpMoveActions(uiRoot); setUpResizeActions(uiRoot); + actions.setupActionButtons(uiRoot); }; diff --git a/question/bank/columnsortorder/classes/local/bank/column_action_remove.php b/question/bank/columnsortorder/classes/local/bank/column_action_remove.php index 97a92e320f3..4bd6a3db9b6 100644 --- a/question/bank/columnsortorder/classes/local/bank/column_action_remove.php +++ b/question/bank/columnsortorder/classes/local/bank/column_action_remove.php @@ -59,7 +59,7 @@ class column_action_remove extends column_action_base { 'column' => $column->get_column_id(), 'action' => 'remove', 'sesskey' => sesskey(), - 'returnurl' => $this->qbank->returnurl, + 'returnurl' => new \moodle_url($this->qbank->returnurl), ]); if ($this->global) { $actionurl->param('global', $this->global); diff --git a/question/classes/local/bank/filter_condition_manager.php b/question/classes/local/bank/filter_condition_manager.php index abf1cf2058f..7939feb7c34 100644 --- a/question/classes/local/bank/filter_condition_manager.php +++ b/question/classes/local/bank/filter_condition_manager.php @@ -37,16 +37,23 @@ class filter_condition_manager { * @return array the param and extra param */ public static function extract_parameters_from_fragment_args(array $args): array { - global $DB; - // Decode query string. - $filtercondition = json_decode($args['filtercondition'], true); - $categories = $DB->get_records('question_categories', ['id' => clean_param($filtercondition['cat'], PARAM_INT)]); - $categories = \qbank_managecategories\helper::question_add_context_in_key($categories); - $category = array_pop($categories); - $filtercondition['cat'] = $category->id; - $extraparams = json_decode($args['extraparams'], true); + $params = []; + if (array_key_exists('filter', $args)) { + $params['filter'] = json_decode($args['filter'], true); + } + if (array_key_exists('cmid', $args)) { + $params['cmid'] = $args['cmid']; + } + if (array_key_exists('courseid', $args)) { + $params['courseid'] = $args['courseid']; + } + $params['jointype'] = $args['jointype'] ?? condition::JOINTYPE_DEFAULT; + $params['qpage'] = $args['qpage'] ?? 0; + $params['qperpage'] = $args['qperpage'] ?? 100; + $params['sortdata'] = json_decode($args['sortdata'] ?? '', true); + $extraparams = json_decode($args['extraparams'] ?? '', true); - return [$filtercondition, $extraparams]; + return [$params, $extraparams]; } /** diff --git a/question/editlib.php b/question/editlib.php index fec2787f91a..78559c9ac53 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -201,6 +201,9 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $unused = // Category list page. $params['cpage'] = optional_param('cpage', null, PARAM_INT); + // Sort data. + $params['sortdata'] = optional_param_array('sortdata', [], PARAM_INT); + $PAGE->set_pagelayout('admin'); return question_build_edit_resources($edittab, $baseurl, $params); @@ -247,7 +250,7 @@ function question_build_edit_resources($edittab, $baseurl, $params, $thispageurl->remove_all_params(); // We are going to explicity add back everything important - this avoids unwanted params from being retained. $cleanparams = [ - 'qsorts' => [], + 'sortdata' => [], 'filter' => null ]; $paramtypes = [ @@ -258,7 +261,6 @@ function question_build_edit_resources($edittab, $baseurl, $params, 'category' => PARAM_SEQUENCE, 'qperpage' => PARAM_INT, 'cpage' => PARAM_INT, - 'qbshowtext' => PARAM_INT, ]; foreach ($paramtypes as $name => $type) { @@ -276,6 +278,10 @@ function question_build_edit_resources($edittab, $baseurl, $params, $cleanparams['filter'] = $params['filter']; } + if (isset($params['sortdata'])) { + $cleanparams['sortdata'] = clean_param_array($params['sortdata'], PARAM_INT); + } + $cmid = $cleanparams['cmid']; $courseid = $cleanparams['courseid']; $qpage = $cleanparams['qpage'] ?: -1; @@ -283,7 +289,6 @@ function question_build_edit_resources($edittab, $baseurl, $params, $category = $cleanparams['category'] ?: 0; $qperpage = $cleanparams['qperpage']; $cpage = $cleanparams['cpage'] ?: 1; - $qsorts = $cleanparams['qsorts']; if (is_null($cmid) && is_null($courseid)) { throw new \moodle_exception('Must provide a cmid or courseid'); @@ -293,16 +298,21 @@ function question_build_edit_resources($edittab, $baseurl, $params, list($module, $cm) = get_module_from_cmid($cmid); $courseid = $cm->course; $thispageurl->params(compact('cmid')); - require_login($courseid, false, $cm); $thiscontext = context_module::instance($cmid); } else { $module = null; $cm = null; $thispageurl->params(compact('courseid')); - require_login($courseid, false); $thiscontext = context_course::instance($courseid); } + if (defined('AJAX_SCRIPT') && AJAX_SCRIPT) { + // For AJAX, we don't need to set up the course page for output. + require_login(); + } else { + require_login($courseid, false, $cm); + } + if ($thiscontext){ $contexts = new core_question\local\bank\question_edit_contexts($thiscontext); $contexts->require_one_edit_tab_cap($edittab); @@ -330,19 +340,6 @@ function question_build_edit_resources($edittab, $baseurl, $params, navigation_node::override_active_url($thispageurl); } - // This need to occur after the override_active_url call above because - // these values change on the page request causing the URLs to mismatch - // when trying to work out the active node. - for ($i = 1; $i <= core_question\local\bank\view::MAX_SORTS; $i++) { - $param = 'qbs' . $i; - if (isset($params[$param])) { - $value = clean_param($params[$param], PARAM_TEXT); - } else { - break; - } - $thispageurl->param($param, $value); - } - if ($pagevars['qpage'] > -1) { $thispageurl->param('qpage', $pagevars['qpage']); } else { @@ -387,7 +384,7 @@ function question_build_edit_resources($edittab, $baseurl, $params, $pagevars['tabname'] = $edittab; // Sort parameters. - $pagevars['sortdata'] = optional_param_array('sortdata', [], PARAM_INT); + $pagevars['sortdata'] = $cleanparams['sortdata']; foreach ($pagevars['sortdata'] as $sortname => $sortorder) { $thispageurl->param('sortdata[' . $sortname . ']', $sortorder); } diff --git a/question/lib.php b/question/lib.php index 50fc3f953e2..60b59a3e6e0 100644 --- a/question/lib.php +++ b/question/lib.php @@ -117,39 +117,33 @@ function core_question_output_fragment_tags_form($args) { * @return array|string */ function core_question_output_fragment_question_data(array $args): string { - global $PAGE; if (empty($args)) { return ''; } [$params, $extraparams] = \core_question\local\bank\filter_condition_manager::extract_parameters_from_fragment_args($args); - $thiscontext = \context_course::instance($params['courseid']); - $contexts = new \core_question\local\bank\question_edit_contexts($thiscontext); - $contexts->require_one_edit_tab_cap($params['tabname']); - $course = get_course($params['courseid']); + [ + $thispageurl, + $contexts, + , + $cm, + , + $pagevars + ] = question_build_edit_resources('questions', '/question/edit.php', $params); + + if (is_null($cm)) { + $course = get_course(clean_param($args['courseid'], PARAM_INT)); + } else { + $course = get_course($cm->course); + } $viewclass = empty($args['view']) ? \core_question\local\bank\view::class : clean_param($args['view'], PARAM_NOTAGS); - $cmid = clean_param($args['cmid'], PARAM_INT); - [, $cm] = empty($cmid) ? [null, null] : get_module_from_cmid($cmid); - $nodeparent = $PAGE->settingsnav->find('questionbank', \navigation_node::TYPE_CONTAINER); - $thispageurl = new \moodle_url($nodeparent->action); - if ($cm) { - $thispageurl->param('cmid', $cm->id); - } else { - $thispageurl->param('courseid', $params['courseid']); - } - if (!empty($args['filtercondition'])) { - $thispageurl->param('filter', $args['filtercondition']); - } if (!empty($args['lastchanged'])) { - $thispageurl->param('lastchanged', $args['lastchanged']); + $thispageurl->param('lastchanged', clean_param($args['lastchanged'], PARAM_INT)); } - if (!empty($params['sortdata'])) { - foreach ($params['sortdata'] as $sortname => $sortorder) { - $thispageurl->param('sortdata[' . $sortname . ']', $sortorder); - } - } - $questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $params, $extraparams); + // This is highly suspicious, but it is the same approach taken in /question/edit.php. See MDL-79281. + $thispageurl->param('deleteall', 1); + $questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $pagevars, $extraparams); $questionbank->add_standard_search_conditions(); ob_start(); $questionbank->display_question_list();