diff --git a/lib/templates/datafilter/filter.mustache b/lib/templates/datafilter/filter.mustache
index 55b4b0e3faf..8acf1afae4c 100644
--- a/lib/templates/datafilter/filter.mustache
+++ b/lib/templates/datafilter/filter.mustache
@@ -36,7 +36,8 @@
}
]
}
- ]
+ ],
+ "showallbutton": false
}
}}
@@ -52,7 +53,10 @@
-
+ {{#showallbutton}}
+
+ {{/showallbutton}}
+
diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php
index c396c784207..2387ef67cd5 100644
--- a/mod/quiz/classes/question/bank/custom_view.php
+++ b/mod/quiz/classes/question/bank/custom_view.php
@@ -83,10 +83,10 @@ class custom_view extends \core_question\local\bank\view {
}
$this->init_columns($this->wanted_columns(), $this->heading_column());
+ $this->pagesize = self::DEFAULT_PAGE_SIZE;
parent::__construct($contexts, $pageurl, $course, $cm, $params, $extraparams);
[$this->quiz, ] = get_module_from_cmid($cm->id);
$this->set_quiz_has_attempts(quiz_has_attempts($this->quiz->id));
- $this->pagesize = self::DEFAULT_PAGE_SIZE;
}
/**
diff --git a/question/amd/build/filter.min.js b/question/amd/build/filter.min.js
index 7c4081df61d..93050f7ebf3 100644
--- a/question/amd/build/filter.min.js
+++ b/question/amd/build/filter.min.js
@@ -1,10 +1,10 @@
-define("core_question/filter",["exports","core/datafilter","core/notification","core/datafilter/selectors","core/templates","core/fragment","core/str"],(function(_exports,_datafilter,_notification,_selectors,_templates,_fragment,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
+define("core_question/filter",["exports","core/datafilter","core/notification","core/datafilter/selectors","core/templates","core/fragment","core/str","core/loadingicon"],(function(_exports,_datafilter,_notification,_selectors,_templates,_fragment,_str,_loadingicon){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Question bank filter management.
*
* @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,_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]",SELECTORS_CATEGORY_VALIDATION_INPUT='div[data-filter-type="category"] div.form-autocomplete-input input',SELECTORS_QUESTION_BANK_WINDOW=".questionbankwindow",filterSet=document.querySelector("#".concat(filterRegionId)),viewData={extraparams:JSON.stringify(extraparams),cmid:cmid,view:view,cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},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)=>{let categoryid=parseInt(filterdata.category.values[0]),categorynode=document.querySelector(SELECTORS_CATEGORY_VALIDATION_INPUT);if(categorynode.setCustomValidity(""),isNaN(categoryid)||categoryid<=0)return(0,_str.get_strings)([{key:"error:category",component:"qbank_managecategories"}]).then((strings=>(categorynode.setCustomValidity(strings[0]),categorynode.reportValidity(),strings))).catch(_notification.default.exception),void pendingPromise.resolve();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.querySelector(SELECTORS_QUESTION_BANK_WINDOW).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.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=async(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]",SELECTORS_CATEGORY_VALIDATION_INPUT='div[data-filter-type="category"] div.form-autocomplete-input input',SELECTORS_QUESTION_BANK_WINDOW=".questionbankwindow",SELECTORS_SHOW_ALL_LINK='[data-filteraction="showall"]',filterSet=document.querySelector("#".concat(filterRegionId)),[showAllText,showPerPageText]=await Promise.all([(0,_str.getString)("showall","core",""),(0,_str.getString)("showperpage","core",extraparams.defaultqperpage)]),viewData={extraparams:JSON.stringify(extraparams),cmid:cmid,view:view,cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},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)=>{let categoryid=parseInt(filterdata.category.values[0]),categorynode=document.querySelector(SELECTORS_CATEGORY_VALIDATION_INPUT);if(categorynode.setCustomValidity(""),isNaN(categoryid)||categoryid<=0)return(0,_str.get_strings)([{key:"error:category",component:"qbank_managecategories"}]).then((strings=>(categorynode.setCustomValidity(strings[0]),categorynode.reportValidity(),strings))).catch(_notification.default.exception),void pendingPromise.resolve();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);const questionscontainer=document.querySelector(SELECTORS_QUESTION_CONTAINER_ID);questionscontainer.innerHTML="",(0,_loadingicon.addIconToContainerRemoveOnCompletion)(questionscontainer,pendingPromise),_fragment.default.loadFragment(component,callback,contextId,viewData).then(((questionhtml,jsfooter)=>(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.querySelector(SELECTORS_QUESTION_BANK_WINDOW).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),showallLink=e.target.closest(SELECTORS_SHOW_ALL_LINK);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)}})(),showallLink&&(e.preventDefault(),0===Number(showallLink.dataset.status)?(viewData.qperpage=extraparams.maxqperpage,showallLink.dataset.status=1,showallLink.innerText=showPerPageText):(viewData.qperpage=extraparams.defaultqperpage,showallLink.dataset.status=0,showallLink.innerText=showAllText),viewData.qpage=0,coreFilter.updateTableFromFilter())}));let jointype=null;if(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 19b10ecd8c9..7dd89178a6f 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';\nimport {get_strings as getStrings} from 'core/str';\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 {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {Object} 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 CATEGORY_VALIDATION_INPUT: 'div[data-filter-type=\"category\"] div.form-autocomplete-input input',\n QUESTION_BANK_WINDOW: '.questionbankwindow',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const viewData = {\n extraparams: JSON.stringify(extraparams),\n cmid,\n view,\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\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\n // MDL-84578 - This is a simple fix for older stable branches, which does not require\n // backporting loads of functionality to validate filters properly.\n let categoryid = parseInt(filterdata.category.values[0]);\n let categorynode = document.querySelector(SELECTORS.CATEGORY_VALIDATION_INPUT);\n categorynode.setCustomValidity('');\n if (isNaN(categoryid) || categoryid <= 0) {\n getStrings([\n {\n key: 'error:category',\n component: 'qbank_managecategories',\n },\n ]).then((strings) => {\n categorynode.setCustomValidity(strings[0]);\n categorynode.reportValidity();\n return strings;\n }).catch(Notification.exception);\n pendingPromise.resolve();\n return;\n }\n\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.querySelector(SELECTORS.QUESTION_BANK_WINDOW).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 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","JSON","stringify","cat","courseid","filter","jointype","qpage","qperpage","sortdata","lastchanged","_document$querySelect2","value","sortData","defaultSort","_document$querySelect3","dataset","_document$querySelect4","defaultsort","parse","coreFilter","CoreFilter","filterdata","pendingPromise","categoryid","parseInt","category","values","categorynode","setCustomValidity","isNaN","key","then","strings","reportValidity","catch","Notification","exception","resolve","filterverb","Object","keys","length","updateUrlParams","loadFragment","questionhtml","jsfooter","questionscontainer","undefined","replaceNode","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","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';\nimport {get_strings as getStrings} from 'core/str';\nimport {getString} from 'core/str';\nimport {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';\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 {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.\n */\nexport const init = async(\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 CATEGORY_VALIDATION_INPUT: 'div[data-filter-type=\"category\"] div.form-autocomplete-input input',\n QUESTION_BANK_WINDOW: '.questionbankwindow',\n SHOW_ALL_LINK: '[data-filteraction=\"showall\"]',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const [\n showAllText,\n showPerPageText,\n ] = await Promise.all([\n getString('showall', 'core', ''),\n getString('showperpage', 'core', extraparams.defaultqperpage),\n ]);\n\n const viewData = {\n extraparams: JSON.stringify(extraparams),\n cmid,\n view,\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\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\n // MDL-84578 - This is a simple fix for older stable branches, which does not require\n // backporting loads of functionality to validate filters properly.\n let categoryid = parseInt(filterdata.category.values[0]);\n let categorynode = document.querySelector(SELECTORS.CATEGORY_VALIDATION_INPUT);\n categorynode.setCustomValidity('');\n if (isNaN(categoryid) || categoryid <= 0) {\n getStrings([\n {\n key: 'error:category',\n component: 'qbank_managecategories',\n },\n ]).then((strings) => {\n categorynode.setCustomValidity(strings[0]);\n categorynode.reportValidity();\n return strings;\n }).catch(Notification.exception);\n pendingPromise.resolve();\n return;\n }\n\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\n const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);\n // Clear the contents of the element, then append the loading icon.\n questionscontainer.innerHTML = '';\n addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise);\n\n Fragment.loadFragment(component, callback, contextId, viewData)\n // Render questions for first page and pagination.\n .then((questionhtml, jsfooter) => {\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.querySelector(SELECTORS.QUESTION_BANK_WINDOW).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 const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK);\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 if (showallLink) {\n\n e.preventDefault();\n\n // Toggle between showing all and going back to the original qperpage.\n if (Number(showallLink.dataset.status) === 0) {\n viewData.qperpage = extraparams.maxqperpage;\n showallLink.dataset.status = 1;\n showallLink.innerText = showPerPageText;\n } else {\n viewData.qperpage = extraparams.defaultqperpage;\n showallLink.dataset.status = 0;\n showallLink.innerText = showAllText;\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter();\n }\n });\n\n // Run apply filter at page load.\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":["async","filterRegionId","defaultcourseid","defaultcategoryid","perpage","contextId","component","callback","view","cmid","pagevars","extraparams","SELECTORS","filterSet","document","querySelector","showAllText","showPerPageText","Promise","all","defaultqperpage","viewData","JSON","stringify","cat","courseid","filter","jointype","qpage","qperpage","sortdata","lastchanged","_document$querySelect2","value","sortData","defaultSort","_document$querySelect3","dataset","_document$querySelect4","defaultsort","parse","coreFilter","CoreFilter","filterdata","pendingPromise","categoryid","parseInt","category","values","categorynode","setCustomValidity","isNaN","key","then","strings","reportValidity","catch","Notification","exception","resolve","filterverb","Object","keys","length","updateUrlParams","questionscontainer","innerHTML","loadFragment","questionhtml","jsfooter","undefined","replaceNode","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","showallLink","preventDefault","oldSort","sortname","sortorder","updateTableFromFilter","paginationURL","getAttribute","get","search","queryString","urlParams","URLSearchParams","has","cleanedUrl","replace","cleanUrlParams","Number","status","maxqperpage","innerText","entries","emptyFilterRow","regions","remove","rowcount","urlFilter","filtertype","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 9549a80f2a6..4d67f621c64 100644
--- a/question/amd/src/filter.js
+++ b/question/amd/src/filter.js
@@ -27,6 +27,9 @@ import Selectors from 'core/datafilter/selectors';
import Templates from 'core/templates';
import Fragment from 'core/fragment';
import {get_strings as getStrings} from 'core/str';
+import {getString} from 'core/str';
+import {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';
+
/**
* Initialise the question bank filter on the element with the given id.
*
@@ -42,7 +45,7 @@ import {get_strings as getStrings} from 'core/str';
* @param {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.
* @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.
*/
-export const init = (
+export const init = async(
filterRegionId,
defaultcourseid,
defaultcategoryid,
@@ -68,10 +71,19 @@ export const init = (
EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',
CATEGORY_VALIDATION_INPUT: 'div[data-filter-type="category"] div.form-autocomplete-input input',
QUESTION_BANK_WINDOW: '.questionbankwindow',
+ SHOW_ALL_LINK: '[data-filteraction="showall"]',
};
const filterSet = document.querySelector(`#${filterRegionId}`);
+ const [
+ showAllText,
+ showPerPageText,
+ ] = await Promise.all([
+ getString('showall', 'core', ''),
+ getString('showperpage', 'core', extraparams.defaultqperpage),
+ ]);
+
const viewData = {
extraparams: JSON.stringify(extraparams),
cmid,
@@ -138,10 +150,15 @@ export const init = (
// Load questions for first page.
viewData.filter = JSON.stringify(filterdata);
viewData.sortdata = JSON.stringify(sortData);
+
+ const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);
+ // Clear the contents of the element, then append the loading icon.
+ questionscontainer.innerHTML = '';
+ addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise);
+
Fragment.loadFragment(component, callback, contextId, viewData)
// Render questions for first page and pagination.
.then((questionhtml, jsfooter) => {
- const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);
if (questionhtml === undefined) {
questionhtml = '';
}
@@ -207,6 +224,7 @@ export const init = (
const sortableLink = e.target.closest(SELECTORS.SORT_LINK);
const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);
const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);
+ const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK);
if (sortableLink) {
e.preventDefault();
const oldSort = sortData;
@@ -232,6 +250,23 @@ export const init = (
if (clearLink) {
cleanUrlParams();
}
+ if (showallLink) {
+
+ e.preventDefault();
+
+ // Toggle between showing all and going back to the original qperpage.
+ if (Number(showallLink.dataset.status) === 0) {
+ viewData.qperpage = extraparams.maxqperpage;
+ showallLink.dataset.status = 1;
+ showallLink.innerText = showPerPageText;
+ } else {
+ viewData.qperpage = extraparams.defaultqperpage;
+ showallLink.dataset.status = 0;
+ showallLink.innerText = showAllText;
+ }
+ viewData.qpage = 0;
+ coreFilter.updateTableFromFilter();
+ }
});
// Run apply filter at page load.
diff --git a/question/classes/local/bank/view.php b/question/classes/local/bank/view.php
index ff5f50851f8..988061f4ecd 100644
--- a/question/classes/local/bank/view.php
+++ b/question/classes/local/bank/view.php
@@ -235,6 +235,10 @@ class view {
$this->cm = $cm;
$this->extraparams = $extraparams;
+ // Add the default qperpage to extra params array so we can switch back and forth between it and "all".
+ $this->extraparams['defaultqperpage'] = $this->pagesize;
+ $this->extraparams['maxqperpage'] = MAXIMUM_QUESTIONS_PER_PAGE;
+
// Default filter condition.
if (!isset($params['filter']) && isset($params['cat'])) {
$params['filter'] = [];
@@ -811,15 +815,18 @@ class view {
*/
protected function load_page_questions(): \moodle_recordset {
global $DB;
+
+ // Load the questions based on the page we are on.
$questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams,
- (int)$this->pagevars['qpage'] * (int)$this->pagevars['qperpage'], $this->pagevars['qperpage']);
+ (int)$this->pagevars['qpage'] * (int)$this->pagevars['qperpage'], (int)$this->pagevars['qperpage']);
+
if (!$questions->valid()) {
$questions->close();
// No questions on this page. Reset to the nearest page that contains questions.
$this->pagevars['qpage'] = max(0,
- ceil($this->totalcount / $this->pagevars['qperpage']) - 1);
+ ceil($this->totalcount / (int)$this->pagevars['qperpage']) - 1);
$questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams,
- $this->pagevars['qpage'] * (int) $this->pagevars['qperpage'], $this->pagevars['qperpage']);
+ (int)$this->pagevars['qpage'] * (int)$this->pagevars['qperpage'], (int)$this->pagevars['qperpage']);
}
return $questions;
}
@@ -1155,6 +1162,7 @@ class view {
// We probably do not want to raise it to unlimited, so randomly picking 5 minutes.
// Note: We do not call this in the loop because quiz ob_ captures this function (see raise() PHP doc).
\core_php_time_limit::raise(300);
+ raise_memory_limit(MEMORY_EXTRA);
[$categoryid, $contextid] = category_condition::validate_category_param($this->pagevars['cat']);
$catcontext = \context::instance_by_id($contextid);
diff --git a/question/classes/output/question_bank_filter_ui.php b/question/classes/output/question_bank_filter_ui.php
index 5f48a823107..c31598801fd 100644
--- a/question/classes/output/question_bank_filter_ui.php
+++ b/question/classes/output/question_bank_filter_ui.php
@@ -137,6 +137,7 @@ class question_bank_filter_ui extends datafilter {
'cmid' => $this->cmid ?? 0,
'pagevars' => json_encode($this->pagevars),
'extraparams' => json_encode($this->extraparams),
+ 'showallbutton' => true,
];
}
}
diff --git a/question/tests/behat/filter_questions_show_all.feature b/question/tests/behat/filter_questions_show_all.feature
new file mode 100644
index 00000000000..2a98877a110
--- /dev/null
+++ b/question/tests/behat/filter_questions_show_all.feature
@@ -0,0 +1,75 @@
+@core @core_question @qbank_filter @javascript
+Feature: A teacher can show all of the questions on the question bank and override pagination
+ In order to see all the questions in the question bank
+ As a teacher
+ I must be able to toggle between show all and show paginated
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | format |
+ | Course 1 | C1 | topics |
+ And the following "activities" exist:
+ | activity | name | course | idnumber |
+ | quiz | Test quiz | C1 | quiz1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "question categories" exist:
+ | contextlevel | reference | questioncategory | name |
+ | Course | C1 | Top | Test Category |
+ Given 201 "questions" exist with the following data:
+ | questioncategory | Test Category |
+ | qtype | truefalse |
+ | name | Question #[count] |
+ | questiontext | True or false? |
+
+ Scenario: Use "Show All" button on the question bank when there are no questions
+ Given I am on the "Course 1" "core_question > course question bank" page logged in as "teacher1"
+ When I apply question bank filter "Category" with value "Default for C1"
+ Then I should not see "Question #"
+ And I click on "Show all" "button"
+ Then I should not see "Question #"
+ And I click on "Show 100 per page" "button"
+ Then I should not see "Question #"
+
+ Scenario: Question bank shows paginated questions by default
+ Given I am on the "Course 1" "core_question > course question bank" page logged in as "teacher1"
+ When I apply question bank filter "Category" with value "Top for Course 1"
+ Then I should see "100" occurrences of "Question #" in the "div#questionscontainer" "css_element"
+ And I should see "1" in the ".pagination" "css_element"
+ And I should see "2" in the ".pagination" "css_element"
+ And I should see "3" in the ".pagination" "css_element"
+
+ Scenario: Toggle "Show all" shows all questions
+ Given I am on the "Course 1" "core_question > course question bank" page logged in as "teacher1"
+ When I apply question bank filter "Category" with value "Top for Course 1"
+ And I click on "Show all" "button"
+ Then I should see "201" occurrences of "Question #" in the "div#questionscontainer" "css_element"
+ And ".pagination" "css_element" should not exist
+ And I should see "Show 100 per page"
+
+ Scenario: Toggle "Show all" back to paginated shows paginated questions
+ Given I am on the "Course 1" "core_question > course question bank" page logged in as "teacher1"
+ When I apply question bank filter "Category" with value "Top for Course 1"
+ And I click on "Show all" "button"
+ And I click on "Show 100 per page" "button"
+ Then I should see "100" occurrences of "Question #" in the "div#questionscontainer" "css_element"
+ And I should see "1" in the ".pagination" "css_element"
+ And I should see "2" in the ".pagination" "css_element"
+ And I should see "3" in the ".pagination" "css_element"
+ And I should see "Show all"
+
+ Scenario: Show all questions on the question bank when adding questions to a quiz
+ Given I am on the "Test quiz" "mod_quiz > Edit" page logged in as "teacher1"
+ When I open the "last" add to quiz menu
+ And I follow "from question bank"
+ And I apply question bank filter "Category" with value "Top for Course 1"
+ Then I should see "20" occurrences of "Question #" in the "div#questionscontainer" "css_element"
+ When I click on "Show all" "button"
+ Then I should see "201" occurrences of "Question #" in the "div#questionscontainer" "css_element"
+ When I click on "Select all" "checkbox"
+ And I click on "Add selected questions to the quiz" "button"
+ Then I should see "Questions: 201"