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 * @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 .\n\n/**\n * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki \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 .\n\n/**\n * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki \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 * @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 .\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 \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 .\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 \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 * @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 .\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 \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}\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 .\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 \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}\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();