diff --git a/grade/report/grader/amd/build/collapse.min.js b/grade/report/grader/amd/build/collapse.min.js index d65e4a9ec00..25deea4815a 100644 --- a/grade/report/grader/amd/build/collapse.min.js +++ b/grade/report/grader/amd/build/collapse.min.js @@ -1,3 +1,3 @@ -define("gradereport_grader/collapse",["exports","gradereport_grader/collapse/repository","core/comboboxsearch/search_combobox","core/templates","core/utils","jquery","core/str","core/custom_interaction_events","core/localstorage","core/loadingicon","core/notification","core/pending"],(function(_exports,Repository,_search_combobox,_templates,_utils,_jquery,_str,_custom_interaction_events,_localstorage,_loadingicon,_notification,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,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),_search_combobox=_interopRequireDefault(_search_combobox),_jquery=_interopRequireDefault(_jquery),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_localstorage=_interopRequireDefault(_localstorage),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const selectors_component=".collapse-columns",selectors_formDropdown=".columnsdropdownform",selectors_formItems={cancel:"cancel",save:"save",checked:'input[type="checkbox"]:checked',currentlyUnchecked:'input[type="checkbox"]:not([data-action="selectall"])'},selectors_hider="hide",selectors_expand="expand",selectors_colVal="[data-col]",selectors_itemVal="[data-itemid]",selectors_content='[data-collapse="content"]',selectors_sort='[data-collapse="sort"]',selectors_expandbutton='[data-collapse="expandbutton"]',selectors_rangerowcell='[data-collapse="rangerowcell"]',selectors_avgrowcell='[data-collapse="avgrowcell"]',selectors_menu='[data-collapse="menu"]',selectors_icons=".data-collapse_gradeicons",selectors_count='[data-collapse="count"]',selectors_placeholder='.collapsecolumndropdown [data-region="placeholder"]',selectors_fullDropdown=".collapsecolumndropdown",countIndicator=document.querySelector(selectors_count);class ColumnSearch extends _search_combobox.default{static init(userID,courseID,defaultSort){return new ColumnSearch(userID,courseID,defaultSort)}constructor(userID,courseID,defaultSort){super(),_defineProperty(this,"userID",-1),_defineProperty(this,"courseID",null),_defineProperty(this,"defaultSort",""),_defineProperty(this,"nodes",[]),_defineProperty(this,"gradeStrings",null),_defineProperty(this,"userStrings",null),_defineProperty(this,"stringMap",[]),this.userID=userID,this.courseID=courseID,this.defaultSort=defaultSort,this.component=document.querySelector(selectors_component);const pendingPromise=new _pending.default;(0,_loadingicon.addIconToContainer)(document.querySelector(".gradeparent")).then((loader=>{setTimeout((()=>{this.getDataset().forEach((item=>{this.nodesUpdate(item)})),this.renderDefault(),loader.remove(),document.querySelector(".gradereport-grader-table").classList.remove("d-none")}),10)})).then((()=>pendingPromise.resolve())).catch(_notification.default.exception)}componentSelector(){return".collapse-columns"}dropdownSelector(){return".searchresultitemscontainer"}triggerSelector(){return".collapsecolumn"}getDataset(){if(!this.dataset){const cols=this.fetchDataset();this.dataset=JSON.parse(cols)?JSON.parse(cols).split(","):[]}return this.datasetSize=this.dataset.length,this.dataset}fetchDataset(){return _localstorage.default.get("gradereport_grader_collapseditems_".concat(this.courseID,"_").concat(this.userID))}setPreferences(){_localstorage.default.set("gradereport_grader_collapseditems_".concat(this.courseID,"_").concat(this.userID),JSON.stringify(this.getDataset().join(",")))}registerClickHandlers(){this.component.addEventListener("click",this.clickHandler.bind(this)),document.addEventListener("click",this.docClickHandler.bind(this))}clickHandler(e){super.clickHandler(e),e.target.closest(selectors_fullDropdown)&&e.stopPropagation()}async docClickHandler(e){var _e$target$closest3;if(e.target.dataset.hider===selectors_hider){var _e$target$closest,_e$target$closest2;e.preventDefault();const desiredToHide=e.target.closest(selectors_colVal)?null===(_e$target$closest=e.target.closest(selectors_colVal))||void 0===_e$target$closest?void 0:_e$target$closest.dataset.col:null===(_e$target$closest2=e.target.closest(selectors_itemVal))||void 0===_e$target$closest2?void 0:_e$target$closest2.dataset.itemid;-1===this.getDataset().indexOf(desiredToHide)&&this.getDataset().push(desiredToHide),await this.prefcountpipe(),this.nodesUpdate(desiredToHide)}if((null===(_e$target$closest3=e.target.closest("button"))||void 0===_e$target$closest3?void 0:_e$target$closest3.dataset.hider)===selectors_expand){var _e$target$closest4,_e$target$closest5,_e$target$closest6,_e$target$closest7;e.preventDefault();const desiredToHide=e.target.closest(selectors_colVal)?null===(_e$target$closest4=e.target.closest(selectors_colVal))||void 0===_e$target$closest4?void 0:_e$target$closest4.dataset.col:null===(_e$target$closest5=e.target.closest(selectors_itemVal))||void 0===_e$target$closest5?void 0:_e$target$closest5.dataset.itemid,idx=this.getDataset().indexOf(desiredToHide);this.getDataset().splice(idx,1),await this.prefcountpipe(),this.nodesUpdate(null===(_e$target$closest6=e.target.closest(selectors_colVal))||void 0===_e$target$closest6?void 0:_e$target$closest6.dataset.col),this.nodesUpdate(null===(_e$target$closest7=e.target.closest(selectors_colVal))||void 0===_e$target$closest7?void 0:_e$target$closest7.dataset.itemid)}}async keyHandler(e){if(super.keyHandler(e),"Tab"===e.key)e.target.closest(this.selectors.input)&&(e.preventDefault(),this.clearSearchButton.focus({preventScroll:!0}))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{if(this.getSearchTerm()===this.searchInput.value&&this.searchResultsVisible())return void window.console.warn("Search term matches input value - skipping");this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none");const pendingPromise=new _pending.default;await this.filterrenderpipe().then((()=>(pendingPromise.resolve(),!0)))}),300,{pending:!0}))}registerFormEvents(){const form=this.component.querySelector(selectors_formDropdown),events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate];_custom_interaction_events.default.define(document,events);const selectall=form.querySelector('[data-action="selectall"]');events.forEach((event=>{const submitBtn=form.querySelector('[data-action="'.concat(selectors_formItems.save,'"'));form.addEventListener(event,(e=>{e.stopPropagation();const input=e.target.closest("input");if(input){selectall.checked&&!input.checked&&(selectall.checked=!1);const checkedCount=Array.from(form.querySelectorAll(selectors_formItems.checked)).length;submitBtn.disabled=checkedCount<=0}}),!1),this.searchInput.addEventListener(event,(e=>e.stopPropagation())),this.clearSearchButton.addEventListener(event,(async e=>{e.stopPropagation(),this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),selectall.addEventListener(event,(e=>{if(e.stopPropagation(),selectall.checked){Array.from(form.querySelectorAll(selectors_formItems.currentlyUnchecked)).forEach((item=>{item.checked=!0})),submitBtn.disabled=!1}else{Array.from(form.querySelectorAll(selectors_formItems.checked)).forEach((item=>{item.checked=!1})),submitBtn.disabled=!0}}))})),form.addEventListener("submit",(async e=>{if(e.preventDefault(),e.submitter.dataset.action===selectors_formItems.cancel)return void(0,_jquery.default)(this.component).dropdown("toggle");[...form.elements].filter((item=>item.checked)).forEach((item=>{const idx=this.getDataset().indexOf(item.dataset.collapse);this.getDataset().splice(idx,1),this.nodesUpdate(item.dataset.collapse)})),selectall.checked=!1,e.submitter.disabled=!0,await this.prefcountpipe()}))}nodesUpdate(item){const colNodesToHide=[...document.querySelectorAll('[data-col="'.concat(item,'"]'))],itemIDNodesToHide=[...document.querySelectorAll('[data-itemid="'.concat(item,'"]'))];this.nodes=[...colNodesToHide,...itemIDNodesToHide],this.updateDisplay()}async prefcountpipe(){this.setPreferences(),this.countUpdate(),await this.filterrenderpipe()}async filterDataset(filterableData){const stringUserMap=await this.fetchRequiredUserStrings(),stringGradeMap=await this.fetchRequiredGradeStrings(),customFieldMap=this.fetchCustomFieldValues();this.stringMap=new Map([...stringGradeMap,...stringUserMap,...customFieldMap]);const searching=filterableData.map((s=>{var _mapObj$itemname,_mapObj$category;const mapObj=this.stringMap.get(s);return void 0===mapObj?{key:s,string:s}:{key:s,string:null!==(_mapObj$itemname=mapObj.itemname)&&void 0!==_mapObj$itemname?_mapObj$itemname:this.stringMap.get(s),category:null!==(_mapObj$category=mapObj.category)&&void 0!==_mapObj$category?_mapObj$category:""}}));return""===this.getPreppedSearchTerm()?searching:searching.filter((col=>col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm())))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((column=>{var _column$string,_column$category;return{name:column.key,displayName:null!==(_column$string=column.string)&&void 0!==_column$string?_column$string:column.key,category:null!==(_column$category=column.category)&&void 0!==_column$category?_column$category:""}})))}updateDisplay(){this.nodes.forEach((element=>{const content=element.querySelector(selectors_content),sort=element.querySelector(selectors_sort),expandButton=element.querySelector(selectors_expandbutton),rangeRowCell=element.querySelector(selectors_rangerowcell),avgRowCell=element.querySelector(selectors_avgrowcell),nodeSet=[element.querySelector(selectors_menu),element.querySelector(selectors_icons),content];if(element.classList.contains("cell"))if(null!==sort&&(window.location=this.defaultSort),null===content){const rowCell=null!=avgRowCell?avgRowCell:rangeRowCell;null==rowCell||rowCell.classList.toggle("d-none")}else content.classList.contains("d-none")?(element.classList.remove("collapsed"),content.childNodes.length>1&&content.classList.add("d-flex"),nodeSet.forEach((node=>{null==node||node.classList.remove("d-none")})),null==expandButton||expandButton.classList.add("d-none")):(element.classList.add("collapsed"),content.classList.remove("d-flex"),nodeSet.forEach((node=>{null==node||node.classList.add("d-none")})),null==expandButton||expandButton.classList.remove("d-none"))}))}countUpdate(){countIndicator.textContent=this.getDatasetSize(),this.getDatasetSize()>0?(this.component.parentElement.classList.add("d-flex"),this.component.parentElement.classList.remove("d-none")):(this.component.parentElement.classList.remove("d-flex"),this.component.parentElement.classList.add("d-none"))}async renderDefault(){this.setMatchedResults(await this.filterDataset(this.getDataset())),this.filterMatchDataset(),this.countUpdate();const{html:html,js:js}=await(0,_templates.renderForPromise)("gradereport_grader/collapse/collapsebody",{results:this.getMatchedResults(),userid:this.userID});(0,_templates.replaceNode)(selectors_placeholder,html,js),this.updateNodes(),this.registerFormEvents(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async renderDropdown(){const selectall=this.component.querySelector(selectors_formDropdown).querySelector('[data-action="selectall"]'),{html:html,js:js}=await(0,_templates.renderForPromise)("gradereport_grader/collapse/collapseresults",{results:this.getMatchedResults(),searchTerm:this.getSearchTerm()});selectall.disabled=0===this.getMatchedResults().length,(0,_templates.replaceNodeContents)(this.getHTMLElements().searchDropdown,html,js)}fetchCustomFieldValues(){return[...document.querySelectorAll("[data-collapse-name]")].map((field=>[field.parentElement.dataset.col,field.dataset.collapseName]))}fetchRequiredUserStrings(){if(!this.userStrings){const requiredStrings=["username","firstname","lastname","email","city","country","department","institution","idnumber","phone1","phone2"];this.userStrings=(0,_str.getStrings)(requiredStrings.map((key=>({key:key})))).then((stringArray=>new Map(requiredStrings.map(((key,index)=>[key,stringArray[index]])))))}return this.userStrings}fetchRequiredGradeStrings(){return this.gradeStrings||(this.gradeStrings=Repository.gradeItems(this.courseID).then((result=>new Map(result.gradeItems.map((key=>[key.id,key])))))),this.gradeStrings}}return _exports.default=ColumnSearch,_exports.default})); +define("gradereport_grader/collapse",["exports","gradereport_grader/collapse/repository","core/comboboxsearch/search_combobox","core/templates","core/utils","jquery","core/str","core/custom_interaction_events","core/localstorage","core/loadingicon","core/notification","core/pending"],(function(_exports,Repository,_search_combobox,_templates,_utils,_jquery,_str,_custom_interaction_events,_localstorage,_loadingicon,_notification,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,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),_search_combobox=_interopRequireDefault(_search_combobox),_jquery=_interopRequireDefault(_jquery),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_localstorage=_interopRequireDefault(_localstorage),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const selectors_component=".collapse-columns",selectors_formDropdown=".columnsdropdownform",selectors_formItems={cancel:"cancel",save:"save",checked:'input[type="checkbox"]:checked',currentlyUnchecked:'input[type="checkbox"]:not([data-action="selectall"])'},selectors_hider="hide",selectors_expand="expand",selectors_colVal="[data-col]",selectors_itemVal="[data-itemid]",selectors_content='[data-collapse="content"]',selectors_sort='[data-collapse="sort"]',selectors_expandbutton='[data-collapse="expandbutton"]',selectors_rangerowcell='[data-collapse="rangerowcell"]',selectors_avgrowcell='[data-collapse="avgrowcell"]',selectors_menu='[data-collapse="menu"]',selectors_icons=".data-collapse_gradeicons",selectors_count='[data-collapse="count"]',selectors_placeholder='.collapsecolumndropdown [data-region="placeholder"]',selectors_fullDropdown=".collapsecolumndropdown",selectors_cellMenuButton=".cellmenubtn",countIndicator=document.querySelector(selectors_count);class ColumnSearch extends _search_combobox.default{static init(userID,courseID,defaultSort){return new ColumnSearch(userID,courseID,defaultSort)}constructor(userID,courseID,defaultSort){super(),_defineProperty(this,"userID",-1),_defineProperty(this,"courseID",null),_defineProperty(this,"defaultSort",""),_defineProperty(this,"nodes",[]),_defineProperty(this,"gradeStrings",null),_defineProperty(this,"userStrings",null),_defineProperty(this,"stringMap",[]),this.userID=userID,this.courseID=courseID,this.defaultSort=defaultSort,this.component=document.querySelector(selectors_component);const pendingPromise=new _pending.default;(0,_loadingicon.addIconToContainer)(document.querySelector(".gradeparent")).then((loader=>{setTimeout((()=>{this.getDataset().forEach((item=>{this.nodesUpdate(item)})),this.renderDefault(),loader.remove(),document.querySelector(".gradereport-grader-table").classList.remove("d-none")}),10)})).then((()=>pendingPromise.resolve())).catch(_notification.default.exception)}componentSelector(){return".collapse-columns"}dropdownSelector(){return".searchresultitemscontainer"}triggerSelector(){return".collapsecolumn"}getDataset(){if(!this.dataset){const cols=this.fetchDataset();this.dataset=JSON.parse(cols)?JSON.parse(cols).split(","):[]}return this.datasetSize=this.dataset.length,this.dataset}fetchDataset(){return _localstorage.default.get("gradereport_grader_collapseditems_".concat(this.courseID,"_").concat(this.userID))}setPreferences(){_localstorage.default.set("gradereport_grader_collapseditems_".concat(this.courseID,"_").concat(this.userID),JSON.stringify(this.getDataset().join(",")))}registerClickHandlers(){this.component.addEventListener("click",this.clickHandler.bind(this)),document.addEventListener("click",this.docClickHandler.bind(this))}clickHandler(e){super.clickHandler(e),e.target.closest(selectors_fullDropdown)&&e.stopPropagation()}async docClickHandler(e){var _e$target$closest3;if(e.target.dataset.hider===selectors_hider){var _e$target$closest,_e$target$closest2;e.preventDefault();const pendingPromise=new _pending.default("gradereport_grader/collapse:docClickHandler:hide"),desiredToHide=e.target.closest(selectors_colVal)?null===(_e$target$closest=e.target.closest(selectors_colVal))||void 0===_e$target$closest?void 0:_e$target$closest.dataset.col:null===(_e$target$closest2=e.target.closest(selectors_itemVal))||void 0===_e$target$closest2?void 0:_e$target$closest2.dataset.itemid;-1===this.getDataset().indexOf(desiredToHide)&&this.getDataset().push(desiredToHide),await this.prefcountpipe(),await this.nodesUpdate(desiredToHide),pendingPromise.resolve()}if((null===(_e$target$closest3=e.target.closest("button"))||void 0===_e$target$closest3?void 0:_e$target$closest3.dataset.hider)===selectors_expand){var _e$target$closest4,_e$target$closest5,_e$target$closest6,_e$target$closest7;e.preventDefault();const pendingPromise=new _pending.default("gradereport_grader/collapse:docClickHandler:expand"),desiredToHide=e.target.closest(selectors_colVal)?null===(_e$target$closest4=e.target.closest(selectors_colVal))||void 0===_e$target$closest4?void 0:_e$target$closest4.dataset.col:null===(_e$target$closest5=e.target.closest(selectors_itemVal))||void 0===_e$target$closest5?void 0:_e$target$closest5.dataset.itemid,idx=this.getDataset().indexOf(desiredToHide);this.getDataset().splice(idx,1),await this.prefcountpipe(),await this.nodesUpdate(null===(_e$target$closest6=e.target.closest(selectors_colVal))||void 0===_e$target$closest6?void 0:_e$target$closest6.dataset.col),await this.nodesUpdate(null===(_e$target$closest7=e.target.closest(selectors_colVal))||void 0===_e$target$closest7?void 0:_e$target$closest7.dataset.itemid),pendingPromise.resolve()}}async keyHandler(e){if(super.keyHandler(e),"Tab"===e.key)e.target.closest(this.selectors.input)&&(e.preventDefault(),this.clearSearchButton.focus({preventScroll:!0}))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{if(this.getSearchTerm()===this.searchInput.value&&this.searchResultsVisible())return void window.console.warn("Search term matches input value - skipping");this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none");const pendingPromise=new _pending.default;await this.filterrenderpipe().then((()=>(pendingPromise.resolve(),!0)))}),300,{pending:!0}))}registerFormEvents(){const form=this.component.querySelector(selectors_formDropdown),events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate];_custom_interaction_events.default.define(document,events);const selectall=form.querySelector('[data-action="selectall"]');events.forEach((event=>{const submitBtn=form.querySelector('[data-action="'.concat(selectors_formItems.save,'"'));form.addEventListener(event,(e=>{e.stopPropagation();const input=e.target.closest("input");if(input){selectall.checked&&!input.checked&&(selectall.checked=!1);const checkedCount=Array.from(form.querySelectorAll(selectors_formItems.checked)).length;submitBtn.disabled=checkedCount<=0}}),!1),this.searchInput.addEventListener(event,(e=>e.stopPropagation())),this.clearSearchButton.addEventListener(event,(async e=>{e.stopPropagation(),this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),selectall.addEventListener(event,(e=>{if(e.stopPropagation(),selectall.checked){Array.from(form.querySelectorAll(selectors_formItems.currentlyUnchecked)).forEach((item=>{item.checked=!0})),submitBtn.disabled=!1}else{Array.from(form.querySelectorAll(selectors_formItems.checked)).forEach((item=>{item.checked=!1})),submitBtn.disabled=!0}}))})),form.addEventListener("submit",(async e=>{if(e.preventDefault(),e.submitter.dataset.action===selectors_formItems.cancel)return void(0,_jquery.default)(this.component).dropdown("toggle");[...form.elements].filter((item=>item.checked)).forEach((item=>{const idx=this.getDataset().indexOf(item.dataset.collapse);this.getDataset().splice(idx,1),this.nodesUpdate(item.dataset.collapse)})),selectall.checked=!1,e.submitter.disabled=!0,await this.prefcountpipe()}))}async nodesUpdate(item){const elements=[...[...document.querySelectorAll('[data-col="'.concat(item,'"]'))],...[...document.querySelectorAll('[data-itemid="'.concat(item,'"]'))]];if(elements&&elements.length){const pendingPromise=new _pending.default("gradereport_grader/collapse:nodesUpdate:"+item);this.updateDisplay(elements).then((()=>pendingPromise.resolve())).catch(_notification.default.exception)}}async prefcountpipe(){this.setPreferences(),this.countUpdate(),await this.filterrenderpipe()}async filterDataset(filterableData){const stringUserMap=await this.fetchRequiredUserStrings(),stringGradeMap=await this.fetchRequiredGradeStrings(),customFieldMap=this.fetchCustomFieldValues();this.stringMap=new Map([...stringGradeMap,...stringUserMap,...customFieldMap]);const searching=filterableData.map((s=>{var _mapObj$itemname,_mapObj$category;const mapObj=this.stringMap.get(s);return void 0===mapObj?{key:s,string:s}:{key:s,string:null!==(_mapObj$itemname=mapObj.itemname)&&void 0!==_mapObj$itemname?_mapObj$itemname:this.stringMap.get(s),category:null!==(_mapObj$category=mapObj.category)&&void 0!==_mapObj$category?_mapObj$category:""}}));return""===this.getPreppedSearchTerm()?searching:searching.filter((col=>col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm())))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((column=>{var _column$string,_column$category;return{name:column.key,displayName:null!==(_column$string=column.string)&&void 0!==_column$string?_column$string:column.key,category:null!==(_column$category=column.category)&&void 0!==_column$category?_column$category:""}})))}async updateDisplay(elements){const promises=[];elements.forEach((element=>{promises.push(this.updateDisplayForElement(element))})),await Promise.all(promises)}async updateDisplayForElement(element){const content=element.querySelector(selectors_content),sort=element.querySelector(selectors_sort),expandButton=element.querySelector(selectors_expandbutton),rangeRowCell=element.querySelector(selectors_rangerowcell),avgRowCell=element.querySelector(selectors_avgrowcell),cellMenuButton=element.querySelector(selectors_cellMenuButton),nodeSet=[element.querySelector(selectors_menu),element.querySelector(selectors_icons),content];if(element.classList.contains("cell"))if(null!==sort&&(window.location=this.defaultSort),null===content){const rowCell=null!=avgRowCell?avgRowCell:rangeRowCell;null==rowCell||rowCell.classList.toggle("d-none")}else content.classList.contains("d-none")?(element.classList.remove("collapsed"),content.childNodes.length>1&&content.classList.add("d-flex"),nodeSet.forEach((node=>{null==node||node.classList.remove("d-none")})),null==expandButton||expandButton.classList.add("d-none"),null==cellMenuButton||cellMenuButton.focus()):(element.classList.add("collapsed"),content.classList.remove("d-flex"),nodeSet.forEach((node=>{null==node||node.classList.add("d-none")})),null==expandButton||expandButton.classList.remove("d-none"))}countUpdate(){countIndicator.textContent=this.getDatasetSize(),this.getDatasetSize()>0?(this.component.parentElement.classList.add("d-flex"),this.component.parentElement.classList.remove("d-none")):(this.component.parentElement.classList.remove("d-flex"),this.component.parentElement.classList.add("d-none"))}async renderDefault(){this.setMatchedResults(await this.filterDataset(this.getDataset())),this.filterMatchDataset(),this.countUpdate();const{html:html,js:js}=await(0,_templates.renderForPromise)("gradereport_grader/collapse/collapsebody",{results:this.getMatchedResults(),userid:this.userID});(0,_templates.replaceNode)(selectors_placeholder,html,js),this.updateNodes(),this.registerFormEvents(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async renderDropdown(){const selectall=this.component.querySelector(selectors_formDropdown).querySelector('[data-action="selectall"]'),{html:html,js:js}=await(0,_templates.renderForPromise)("gradereport_grader/collapse/collapseresults",{results:this.getMatchedResults(),searchTerm:this.getSearchTerm()});selectall.disabled=0===this.getMatchedResults().length,(0,_templates.replaceNodeContents)(this.getHTMLElements().searchDropdown,html,js)}fetchCustomFieldValues(){return[...document.querySelectorAll("[data-collapse-name]")].map((field=>[field.parentElement.dataset.col,field.dataset.collapseName]))}fetchRequiredUserStrings(){if(!this.userStrings){const requiredStrings=["username","firstname","lastname","email","city","country","department","institution","idnumber","phone1","phone2"];this.userStrings=(0,_str.getStrings)(requiredStrings.map((key=>({key:key})))).then((stringArray=>new Map(requiredStrings.map(((key,index)=>[key,stringArray[index]])))))}return this.userStrings}fetchRequiredGradeStrings(){return this.gradeStrings||(this.gradeStrings=Repository.gradeItems(this.courseID).then((result=>new Map(result.gradeItems.map((key=>[key.id,key])))))),this.gradeStrings}}return _exports.default=ColumnSearch,_exports.default})); //# sourceMappingURL=collapse.min.js.map \ No newline at end of file diff --git a/grade/report/grader/amd/build/collapse.min.js.map b/grade/report/grader/amd/build/collapse.min.js.map index b7c4f8f2027..d8b6a66e9bf 100644 --- a/grade/report/grader/amd/build/collapse.min.js.map +++ b/grade/report/grader/amd/build/collapse.min.js.map @@ -1 +1 @@ -{"version":3,"file":"collapse.min.js","sources":["../src/collapse.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 * Allow the user to show and hide columns of the report at will.\n *\n * @module gradereport_grader/collapse\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Repository from 'gradereport_grader/collapse/repository';\nimport search_combobox from 'core/comboboxsearch/search_combobox';\nimport {renderForPromise, replaceNodeContents, replaceNode} from 'core/templates';\nimport {debounce} from 'core/utils';\nimport $ from 'jquery';\nimport {getStrings} from 'core/str';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport storage from 'core/localstorage';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n component: '.collapse-columns',\n formDropdown: '.columnsdropdownform',\n formItems: {\n cancel: 'cancel',\n save: 'save',\n checked: 'input[type=\"checkbox\"]:checked',\n currentlyUnchecked: 'input[type=\"checkbox\"]:not([data-action=\"selectall\"])',\n },\n hider: 'hide',\n expand: 'expand',\n colVal: '[data-col]',\n itemVal: '[data-itemid]',\n content: '[data-collapse=\"content\"]',\n sort: '[data-collapse=\"sort\"]',\n expandbutton: '[data-collapse=\"expandbutton\"]',\n rangerowcell: '[data-collapse=\"rangerowcell\"]',\n avgrowcell: '[data-collapse=\"avgrowcell\"]',\n menu: '[data-collapse=\"menu\"]',\n icons: '.data-collapse_gradeicons',\n count: '[data-collapse=\"count\"]',\n placeholder: '.collapsecolumndropdown [data-region=\"placeholder\"]',\n fullDropdown: '.collapsecolumndropdown',\n};\n\nconst countIndicator = document.querySelector(selectors.count);\n\nexport default class ColumnSearch extends search_combobox {\n\n userID = -1;\n courseID = null;\n defaultSort = '';\n\n nodes = [];\n\n gradeStrings = null;\n userStrings = null;\n stringMap = [];\n\n static init(userID, courseID, defaultSort) {\n return new ColumnSearch(userID, courseID, defaultSort);\n }\n\n constructor(userID, courseID, defaultSort) {\n super();\n this.userID = userID;\n this.courseID = courseID;\n this.defaultSort = defaultSort;\n this.component = document.querySelector(selectors.component);\n\n const pendingPromise = new Pending();\n // Display a loader whilst collapsing appropriate columns (based on the locally stored state for the current user).\n addIconToContainer(document.querySelector('.gradeparent')).then((loader) => {\n setTimeout(() => {\n // Get the users' checked columns to change.\n this.getDataset().forEach((item) => {\n this.nodesUpdate(item);\n });\n this.renderDefault();\n\n // Once the grade categories have been re-collapsed, remove the loader and display the Gradebook setup content.\n loader.remove();\n document.querySelector('.gradereport-grader-table').classList.remove('d-none');\n }, 10);\n }).then(() => pendingPromise.resolve()).catch(Notification.exception);\n }\n\n /**\n * The overall div that contains the searching widget.\n *\n * @returns {string}\n */\n componentSelector() {\n return '.collapse-columns';\n }\n\n /**\n * The dropdown div that contains the searching widget result space.\n *\n * @returns {string}\n */\n dropdownSelector() {\n return '.searchresultitemscontainer';\n }\n\n /**\n * The triggering div that contains the searching widget.\n *\n * @returns {string}\n */\n triggerSelector() {\n return '.collapsecolumn';\n }\n\n /**\n * Return the dataset that we will be searching upon.\n *\n * @returns {Array}\n */\n getDataset() {\n if (!this.dataset) {\n const cols = this.fetchDataset();\n this.dataset = JSON.parse(cols) ? JSON.parse(cols).split(',') : [];\n }\n this.datasetSize = this.dataset.length;\n return this.dataset;\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {string}\n */\n fetchDataset() {\n return storage.get(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`);\n }\n\n /**\n * Given a user performs an action, update the users' preferences.\n */\n setPreferences() {\n storage.set(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`,\n JSON.stringify(this.getDataset().join(','))\n );\n }\n\n /**\n * Register clickable event listeners.\n */\n registerClickHandlers() {\n // Register click events within the component.\n this.component.addEventListener('click', this.clickHandler.bind(this));\n\n document.addEventListener('click', this.docClickHandler.bind(this));\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n clickHandler(e) {\n super.clickHandler(e);\n // Prevent BS from closing the dropdown if they click elsewhere within the dropdown besides the form.\n if (e.target.closest(selectors.fullDropdown)) {\n e.stopPropagation();\n }\n }\n\n /**\n * Externally defined click function to improve memory handling.\n *\n * @param {MouseEvent} e\n * @returns {Promise}\n */\n async docClickHandler(e) {\n if (e.target.dataset.hider === selectors.hider) {\n e.preventDefault();\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.closest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n if (idx === -1) {\n this.getDataset().push(desiredToHide);\n }\n await this.prefcountpipe();\n\n this.nodesUpdate(desiredToHide);\n }\n\n if (e.target.closest('button')?.dataset.hider === selectors.expand) {\n e.preventDefault();\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.closest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n this.getDataset().splice(idx, 1);\n\n await this.prefcountpipe();\n\n this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col);\n this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid);\n }\n }\n\n /**\n * The handler for when a user presses a key within the component.\n *\n * @param {KeyboardEvent} e The triggering event that we are working with.\n */\n async keyHandler(e) {\n super.keyHandler(e);\n\n // Switch the key presses to handle keyboard nav.\n switch (e.key) {\n case 'Tab':\n if (e.target.closest(this.selectors.input)) {\n e.preventDefault();\n this.clearSearchButton.focus({preventScroll: true});\n }\n break;\n }\n }\n\n /**\n * Handle any keyboard inputs.\n */\n registerInputEvents() {\n // Register & handle the text input.\n this.searchInput.addEventListener('input', debounce(async() => {\n if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n window.console.warn(`Search term matches input value - skipping`);\n // Debounce can happen multiple times quickly.\n return;\n }\n this.setSearchTerms(this.searchInput.value);\n // We can also require a set amount of input before search.\n if (this.searchInput.value === '') {\n // Hide the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.add('d-none');\n } else {\n // Display the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.remove('d-none');\n }\n const pendingPromise = new Pending();\n // User has given something for us to filter against.\n await this.filterrenderpipe().then(() => {\n pendingPromise.resolve();\n return true;\n });\n }, 300, {pending: true}));\n }\n\n /**\n * Handle the form submission within the dropdown.\n */\n registerFormEvents() {\n const form = this.component.querySelector(selectors.formDropdown);\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n\n // Register clicks & keyboard form handling.\n events.forEach((event) => {\n const submitBtn = form.querySelector(`[data-action=\"${selectors.formItems.save}\"`);\n form.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n const input = e.target.closest('input');\n if (input) {\n // If the user is unchecking an item, we need to uncheck the select all if it's checked.\n if (selectall.checked && !input.checked) {\n selectall.checked = false;\n }\n const checkedCount = Array.from(form.querySelectorAll(selectors.formItems.checked)).length;\n // Check if any are clicked or not then change disabled.\n submitBtn.disabled = checkedCount <= 0;\n }\n }, false);\n\n // Stop Bootstrap from being clever.\n this.searchInput.addEventListener(event, e => e.stopPropagation());\n this.clearSearchButton.addEventListener(event, async(e) => {\n e.stopPropagation();\n this.searchInput.value = '';\n this.setSearchTerms(this.searchInput.value);\n await this.filterrenderpipe();\n });\n selectall.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n if (!selectall.checked) {\n const touncheck = Array.from(form.querySelectorAll(selectors.formItems.checked));\n touncheck.forEach(item => {\n item.checked = false;\n });\n submitBtn.disabled = true;\n } else {\n const currentUnchecked = Array.from(form.querySelectorAll(selectors.formItems.currentlyUnchecked));\n currentUnchecked.forEach(item => {\n item.checked = true;\n });\n submitBtn.disabled = false;\n }\n });\n });\n\n form.addEventListener('submit', async(e) => {\n e.preventDefault();\n if (e.submitter.dataset.action === selectors.formItems.cancel) {\n $(this.component).dropdown('toggle');\n return;\n }\n // Get the users' checked columns to change.\n const checkedItems = [...form.elements].filter(item => item.checked);\n checkedItems.forEach((item) => {\n const idx = this.getDataset().indexOf(item.dataset.collapse);\n this.getDataset().splice(idx, 1);\n this.nodesUpdate(item.dataset.collapse);\n });\n // Reset the check all & submit to false just in case.\n selectall.checked = false;\n e.submitter.disabled = true;\n await this.prefcountpipe();\n });\n }\n\n nodesUpdate(item) {\n const colNodesToHide = [...document.querySelectorAll(`[data-col=\"${item}\"]`)];\n const itemIDNodesToHide = [...document.querySelectorAll(`[data-itemid=\"${item}\"]`)];\n this.nodes = [...colNodesToHide, ...itemIDNodesToHide];\n this.updateDisplay();\n }\n\n /**\n * Update the user preferences, count display then render the results.\n *\n * @returns {Promise}\n */\n async prefcountpipe() {\n this.setPreferences();\n this.countUpdate();\n await this.filterrenderpipe();\n }\n\n /**\n * Dictate to the search component how and what we want to match upon.\n *\n * @param {Array} filterableData\n * @returns {Array} An array of objects containing the system reference and the user readable value.\n */\n async filterDataset(filterableData) {\n const stringUserMap = await this.fetchRequiredUserStrings();\n const stringGradeMap = await this.fetchRequiredGradeStrings();\n // Custom user profile fields are not in our string map and need a bit of extra love.\n const customFieldMap = this.fetchCustomFieldValues();\n this.stringMap = new Map([...stringGradeMap, ...stringUserMap, ...customFieldMap]);\n\n const searching = filterableData.map(s => {\n const mapObj = this.stringMap.get(s);\n if (mapObj === undefined) {\n return {key: s, string: s};\n }\n return {\n key: s,\n string: mapObj.itemname ?? this.stringMap.get(s),\n category: mapObj.category ?? '',\n };\n });\n // Sometimes we just want to show everything.\n if (this.getPreppedSearchTerm() === '') {\n return searching;\n }\n // Other times we want to actually filter the content.\n return searching.filter((col) => {\n return col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm());\n });\n }\n\n /**\n * Given we have a subset of the dataset, set the field that we matched upon to inform the end user.\n */\n filterMatchDataset() {\n this.setMatchedResults(\n this.getMatchedResults().map((column) => {\n return {\n name: column.key,\n displayName: column.string ?? column.key,\n category: column.category ?? '',\n };\n })\n );\n }\n\n /**\n * With an array of nodes, switch their classes and values.\n */\n updateDisplay() {\n this.nodes.forEach((element) => {\n const content = element.querySelector(selectors.content);\n const sort = element.querySelector(selectors.sort);\n const expandButton = element.querySelector(selectors.expandbutton);\n const rangeRowCell = element.querySelector(selectors.rangerowcell);\n const avgRowCell = element.querySelector(selectors.avgrowcell);\n const nodeSet = [\n element.querySelector(selectors.menu),\n element.querySelector(selectors.icons),\n content\n ];\n\n // This can be further improved to reduce redundant similar calls.\n if (element.classList.contains('cell')) {\n // The column is actively being sorted, lets reset that and reload the page.\n if (sort !== null) {\n window.location = this.defaultSort;\n }\n if (content === null) {\n // If it's not a content cell, it must be an overall average or a range cell.\n const rowCell = avgRowCell ?? rangeRowCell;\n\n rowCell?.classList.toggle('d-none');\n } else if (content.classList.contains('d-none')) {\n // We should always have content but some cells do not contain menus or other actions.\n element.classList.remove('collapsed');\n // If there are many nodes, apply the following.\n if (content.childNodes.length > 1) {\n content.classList.add('d-flex');\n }\n nodeSet.forEach(node => {\n node?.classList.remove('d-none');\n });\n expandButton?.classList.add('d-none');\n } else {\n element.classList.add('collapsed');\n content.classList.remove('d-flex');\n nodeSet.forEach(node => {\n node?.classList.add('d-none');\n });\n expandButton?.classList.remove('d-none');\n }\n }\n });\n }\n\n /**\n * Update the visual count of collapsed columns or hide the count all together.\n */\n countUpdate() {\n countIndicator.textContent = this.getDatasetSize();\n if (this.getDatasetSize() > 0) {\n this.component.parentElement.classList.add('d-flex');\n this.component.parentElement.classList.remove('d-none');\n } else {\n this.component.parentElement.classList.remove('d-flex');\n this.component.parentElement.classList.add('d-none');\n }\n }\n\n /**\n * Build the content then replace the node by default we want our form to exist.\n */\n async renderDefault() {\n this.setMatchedResults(await this.filterDataset(this.getDataset()));\n this.filterMatchDataset();\n\n // Update the collapsed button pill.\n this.countUpdate();\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapsebody', {\n 'results': this.getMatchedResults(),\n 'userid': this.userID,\n });\n replaceNode(selectors.placeholder, html, js);\n this.updateNodes();\n\n // Given we now have the body, we can set up more triggers.\n this.registerFormEvents();\n this.registerInputEvents();\n\n // Add a small BS listener so that we can set the focus correctly on open.\n this.$component.on('shown.bs.dropdown', () => {\n this.searchInput.focus({preventScroll: true});\n });\n }\n\n /**\n * Build the content then replace the node.\n */\n async renderDropdown() {\n const form = this.component.querySelector(selectors.formDropdown);\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapseresults', {\n 'results': this.getMatchedResults(),\n 'searchTerm': this.getSearchTerm(),\n });\n selectall.disabled = this.getMatchedResults().length === 0;\n replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);\n }\n\n /**\n * If we have any custom user profile fields, grab their system & readable names to add to our string map.\n *\n * @returns {array} An array of associated string arrays ready for our map.\n */\n fetchCustomFieldValues() {\n const customFields = document.querySelectorAll('[data-collapse-name]');\n // Cast from NodeList to array to grab all the values.\n return [...customFields].map(field => [field.parentElement.dataset.col, field.dataset.collapseName]);\n }\n\n /**\n * Given the set of profile fields we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promise}\n */\n fetchRequiredUserStrings() {\n if (!this.userStrings) {\n const requiredStrings = [\n 'username',\n 'firstname',\n 'lastname',\n 'email',\n 'city',\n 'country',\n 'department',\n 'institution',\n 'idnumber',\n 'phone1',\n 'phone2',\n ];\n this.userStrings = getStrings(requiredStrings.map((key) => ({key})))\n .then((stringArray) => new Map(\n requiredStrings.map((key, index) => ([key, stringArray[index]]))\n ));\n }\n return this.userStrings;\n }\n\n /**\n * Given the set of gradable items we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promise}\n */\n fetchRequiredGradeStrings() {\n if (!this.gradeStrings) {\n this.gradeStrings = Repository.gradeItems(this.courseID)\n .then((result) => new Map(\n result.gradeItems.map(key => ([key.id, key]))\n ));\n }\n return this.gradeStrings;\n }\n}\n"],"names":["selectors","cancel","save","checked","currentlyUnchecked","countIndicator","document","querySelector","ColumnSearch","search_combobox","userID","courseID","defaultSort","constructor","component","pendingPromise","Pending","then","loader","setTimeout","getDataset","forEach","item","nodesUpdate","renderDefault","remove","classList","resolve","catch","Notification","exception","componentSelector","dropdownSelector","triggerSelector","this","dataset","cols","fetchDataset","JSON","parse","split","datasetSize","length","storage","get","setPreferences","set","stringify","join","registerClickHandlers","addEventListener","clickHandler","bind","docClickHandler","e","target","closest","stopPropagation","hider","preventDefault","desiredToHide","_e$target$closest","col","_e$target$closest2","itemid","indexOf","push","prefcountpipe","_e$target$closest4","_e$target$closest5","idx","splice","_e$target$closest6","_e$target$closest7","keyHandler","key","input","clearSearchButton","focus","preventScroll","registerInputEvents","searchInput","async","getSearchTerm","value","searchResultsVisible","window","console","warn","setSearchTerms","add","filterrenderpipe","pending","registerFormEvents","form","events","CustomEvents","activate","keyboardActivate","define","selectall","event","submitBtn","checkedCount","Array","from","querySelectorAll","disabled","submitter","action","dropdown","elements","filter","collapse","colNodesToHide","itemIDNodesToHide","nodes","updateDisplay","countUpdate","filterableData","stringUserMap","fetchRequiredUserStrings","stringGradeMap","fetchRequiredGradeStrings","customFieldMap","fetchCustomFieldValues","stringMap","Map","searching","map","s","mapObj","undefined","string","itemname","category","getPreppedSearchTerm","toString","toLowerCase","includes","filterMatchDataset","setMatchedResults","getMatchedResults","column","name","displayName","element","content","sort","expandButton","rangeRowCell","avgRowCell","nodeSet","contains","location","rowCell","toggle","childNodes","node","textContent","getDatasetSize","parentElement","filterDataset","html","js","updateNodes","$component","on","getHTMLElements","searchDropdown","field","collapseName","userStrings","requiredStrings","stringArray","index","gradeStrings","Repository","gradeItems","result","id"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"collapse.min.js","sources":["../src/collapse.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 * Allow the user to show and hide columns of the report at will.\n *\n * @module gradereport_grader/collapse\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Repository from 'gradereport_grader/collapse/repository';\nimport search_combobox from 'core/comboboxsearch/search_combobox';\nimport {renderForPromise, replaceNodeContents, replaceNode} from 'core/templates';\nimport {debounce} from 'core/utils';\nimport $ from 'jquery';\nimport {getStrings} from 'core/str';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport storage from 'core/localstorage';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n component: '.collapse-columns',\n formDropdown: '.columnsdropdownform',\n formItems: {\n cancel: 'cancel',\n save: 'save',\n checked: 'input[type=\"checkbox\"]:checked',\n currentlyUnchecked: 'input[type=\"checkbox\"]:not([data-action=\"selectall\"])',\n },\n hider: 'hide',\n expand: 'expand',\n colVal: '[data-col]',\n itemVal: '[data-itemid]',\n content: '[data-collapse=\"content\"]',\n sort: '[data-collapse=\"sort\"]',\n expandbutton: '[data-collapse=\"expandbutton\"]',\n rangerowcell: '[data-collapse=\"rangerowcell\"]',\n avgrowcell: '[data-collapse=\"avgrowcell\"]',\n menu: '[data-collapse=\"menu\"]',\n icons: '.data-collapse_gradeicons',\n count: '[data-collapse=\"count\"]',\n placeholder: '.collapsecolumndropdown [data-region=\"placeholder\"]',\n fullDropdown: '.collapsecolumndropdown',\n cellMenuButton: '.cellmenubtn',\n};\n\nconst countIndicator = document.querySelector(selectors.count);\n\nexport default class ColumnSearch extends search_combobox {\n\n userID = -1;\n courseID = null;\n defaultSort = '';\n\n nodes = [];\n\n gradeStrings = null;\n userStrings = null;\n stringMap = [];\n\n static init(userID, courseID, defaultSort) {\n return new ColumnSearch(userID, courseID, defaultSort);\n }\n\n constructor(userID, courseID, defaultSort) {\n super();\n this.userID = userID;\n this.courseID = courseID;\n this.defaultSort = defaultSort;\n this.component = document.querySelector(selectors.component);\n\n const pendingPromise = new Pending();\n // Display a loader whilst collapsing appropriate columns (based on the locally stored state for the current user).\n addIconToContainer(document.querySelector('.gradeparent')).then((loader) => {\n setTimeout(() => {\n // Get the users' checked columns to change.\n this.getDataset().forEach((item) => {\n this.nodesUpdate(item);\n });\n this.renderDefault();\n\n // Once the grade categories have been re-collapsed, remove the loader and display the Gradebook setup content.\n loader.remove();\n document.querySelector('.gradereport-grader-table').classList.remove('d-none');\n }, 10);\n }).then(() => pendingPromise.resolve()).catch(Notification.exception);\n }\n\n /**\n * The overall div that contains the searching widget.\n *\n * @returns {string}\n */\n componentSelector() {\n return '.collapse-columns';\n }\n\n /**\n * The dropdown div that contains the searching widget result space.\n *\n * @returns {string}\n */\n dropdownSelector() {\n return '.searchresultitemscontainer';\n }\n\n /**\n * The triggering div that contains the searching widget.\n *\n * @returns {string}\n */\n triggerSelector() {\n return '.collapsecolumn';\n }\n\n /**\n * Return the dataset that we will be searching upon.\n *\n * @returns {Array}\n */\n getDataset() {\n if (!this.dataset) {\n const cols = this.fetchDataset();\n this.dataset = JSON.parse(cols) ? JSON.parse(cols).split(',') : [];\n }\n this.datasetSize = this.dataset.length;\n return this.dataset;\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {string}\n */\n fetchDataset() {\n return storage.get(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`);\n }\n\n /**\n * Given a user performs an action, update the users' preferences.\n */\n setPreferences() {\n storage.set(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`,\n JSON.stringify(this.getDataset().join(','))\n );\n }\n\n /**\n * Register clickable event listeners.\n */\n registerClickHandlers() {\n // Register click events within the component.\n this.component.addEventListener('click', this.clickHandler.bind(this));\n\n document.addEventListener('click', this.docClickHandler.bind(this));\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n clickHandler(e) {\n super.clickHandler(e);\n // Prevent BS from closing the dropdown if they click elsewhere within the dropdown besides the form.\n if (e.target.closest(selectors.fullDropdown)) {\n e.stopPropagation();\n }\n }\n\n /**\n * Externally defined click function to improve memory handling.\n *\n * @param {MouseEvent} e\n * @returns {Promise}\n */\n async docClickHandler(e) {\n if (e.target.dataset.hider === selectors.hider) {\n e.preventDefault();\n const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:hide');\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.closest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n if (idx === -1) {\n this.getDataset().push(desiredToHide);\n }\n await this.prefcountpipe();\n\n await this.nodesUpdate(desiredToHide);\n pendingPromise.resolve();\n }\n\n if (e.target.closest('button')?.dataset.hider === selectors.expand) {\n e.preventDefault();\n const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:expand');\n const desiredToHide = e.target.closest(selectors.colVal) ?\n e.target.closest(selectors.colVal)?.dataset.col :\n e.target.closest(selectors.itemVal)?.dataset.itemid;\n const idx = this.getDataset().indexOf(desiredToHide);\n this.getDataset().splice(idx, 1);\n\n await this.prefcountpipe();\n\n await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col);\n await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid);\n pendingPromise.resolve();\n }\n }\n\n /**\n * The handler for when a user presses a key within the component.\n *\n * @param {KeyboardEvent} e The triggering event that we are working with.\n */\n async keyHandler(e) {\n super.keyHandler(e);\n\n // Switch the key presses to handle keyboard nav.\n switch (e.key) {\n case 'Tab':\n if (e.target.closest(this.selectors.input)) {\n e.preventDefault();\n this.clearSearchButton.focus({preventScroll: true});\n }\n break;\n }\n }\n\n /**\n * Handle any keyboard inputs.\n */\n registerInputEvents() {\n // Register & handle the text input.\n this.searchInput.addEventListener('input', debounce(async() => {\n if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n window.console.warn(`Search term matches input value - skipping`);\n // Debounce can happen multiple times quickly.\n return;\n }\n this.setSearchTerms(this.searchInput.value);\n // We can also require a set amount of input before search.\n if (this.searchInput.value === '') {\n // Hide the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.add('d-none');\n } else {\n // Display the \"clear\" search button in the search bar.\n this.clearSearchButton.classList.remove('d-none');\n }\n const pendingPromise = new Pending();\n // User has given something for us to filter against.\n await this.filterrenderpipe().then(() => {\n pendingPromise.resolve();\n return true;\n });\n }, 300, {pending: true}));\n }\n\n /**\n * Handle the form submission within the dropdown.\n */\n registerFormEvents() {\n const form = this.component.querySelector(selectors.formDropdown);\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n\n // Register clicks & keyboard form handling.\n events.forEach((event) => {\n const submitBtn = form.querySelector(`[data-action=\"${selectors.formItems.save}\"`);\n form.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n const input = e.target.closest('input');\n if (input) {\n // If the user is unchecking an item, we need to uncheck the select all if it's checked.\n if (selectall.checked && !input.checked) {\n selectall.checked = false;\n }\n const checkedCount = Array.from(form.querySelectorAll(selectors.formItems.checked)).length;\n // Check if any are clicked or not then change disabled.\n submitBtn.disabled = checkedCount <= 0;\n }\n }, false);\n\n // Stop Bootstrap from being clever.\n this.searchInput.addEventListener(event, e => e.stopPropagation());\n this.clearSearchButton.addEventListener(event, async(e) => {\n e.stopPropagation();\n this.searchInput.value = '';\n this.setSearchTerms(this.searchInput.value);\n await this.filterrenderpipe();\n });\n selectall.addEventListener(event, (e) => {\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n if (!selectall.checked) {\n const touncheck = Array.from(form.querySelectorAll(selectors.formItems.checked));\n touncheck.forEach(item => {\n item.checked = false;\n });\n submitBtn.disabled = true;\n } else {\n const currentUnchecked = Array.from(form.querySelectorAll(selectors.formItems.currentlyUnchecked));\n currentUnchecked.forEach(item => {\n item.checked = true;\n });\n submitBtn.disabled = false;\n }\n });\n });\n\n form.addEventListener('submit', async(e) => {\n e.preventDefault();\n if (e.submitter.dataset.action === selectors.formItems.cancel) {\n $(this.component).dropdown('toggle');\n return;\n }\n // Get the users' checked columns to change.\n const checkedItems = [...form.elements].filter(item => item.checked);\n checkedItems.forEach((item) => {\n const idx = this.getDataset().indexOf(item.dataset.collapse);\n this.getDataset().splice(idx, 1);\n this.nodesUpdate(item.dataset.collapse);\n });\n // Reset the check all & submit to false just in case.\n selectall.checked = false;\n e.submitter.disabled = true;\n await this.prefcountpipe();\n });\n }\n\n async nodesUpdate(item) {\n const colNodesToHide = [...document.querySelectorAll(`[data-col=\"${item}\"]`)];\n const itemIDNodesToHide = [...document.querySelectorAll(`[data-itemid=\"${item}\"]`)];\n const elements = [...colNodesToHide, ...itemIDNodesToHide];\n if (elements && elements.length) {\n const pendingPromise = new Pending('gradereport_grader/collapse:nodesUpdate:' + item);\n this.updateDisplay(elements).then(() => pendingPromise.resolve()).catch(Notification.exception);\n }\n }\n\n /**\n * Update the user preferences, count display then render the results.\n *\n * @returns {Promise}\n */\n async prefcountpipe() {\n this.setPreferences();\n this.countUpdate();\n await this.filterrenderpipe();\n }\n\n /**\n * Dictate to the search component how and what we want to match upon.\n *\n * @param {Array} filterableData\n * @returns {Array} An array of objects containing the system reference and the user readable value.\n */\n async filterDataset(filterableData) {\n const stringUserMap = await this.fetchRequiredUserStrings();\n const stringGradeMap = await this.fetchRequiredGradeStrings();\n // Custom user profile fields are not in our string map and need a bit of extra love.\n const customFieldMap = this.fetchCustomFieldValues();\n this.stringMap = new Map([...stringGradeMap, ...stringUserMap, ...customFieldMap]);\n\n const searching = filterableData.map(s => {\n const mapObj = this.stringMap.get(s);\n if (mapObj === undefined) {\n return {key: s, string: s};\n }\n return {\n key: s,\n string: mapObj.itemname ?? this.stringMap.get(s),\n category: mapObj.category ?? '',\n };\n });\n // Sometimes we just want to show everything.\n if (this.getPreppedSearchTerm() === '') {\n return searching;\n }\n // Other times we want to actually filter the content.\n return searching.filter((col) => {\n return col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm());\n });\n }\n\n /**\n * Given we have a subset of the dataset, set the field that we matched upon to inform the end user.\n */\n filterMatchDataset() {\n this.setMatchedResults(\n this.getMatchedResults().map((column) => {\n return {\n name: column.key,\n displayName: column.string ?? column.key,\n category: column.category ?? '',\n };\n })\n );\n }\n\n /**\n * With an array of nodes, switch their classes and values.\n *\n * @param {Array} elements The elements to update.\n */\n async updateDisplay(elements) {\n const promises = [];\n elements.forEach((element) => {\n promises.push(this.updateDisplayForElement(element));\n });\n\n await Promise.all(promises);\n }\n\n /**\n * Update display for given element, switch its classes and values.\n *\n * @param {HTMLElement} element The element to update.\n */\n async updateDisplayForElement(element) {\n const content = element.querySelector(selectors.content);\n const sort = element.querySelector(selectors.sort);\n const expandButton = element.querySelector(selectors.expandbutton);\n const rangeRowCell = element.querySelector(selectors.rangerowcell);\n const avgRowCell = element.querySelector(selectors.avgrowcell);\n const cellMenuButton = element.querySelector(selectors.cellMenuButton);\n const nodeSet = [\n element.querySelector(selectors.menu),\n element.querySelector(selectors.icons),\n content\n ];\n\n // This can be further improved to reduce redundant similar calls.\n if (element.classList.contains('cell')) {\n // The column is actively being sorted, lets reset that and reload the page.\n if (sort !== null) {\n window.location = this.defaultSort;\n }\n if (content === null) {\n // If it's not a content cell, it must be an overall average or a range cell.\n const rowCell = avgRowCell ?? rangeRowCell;\n\n rowCell?.classList.toggle('d-none');\n } else if (content.classList.contains('d-none')) {\n // We should always have content but some cells do not contain menus or other actions.\n element.classList.remove('collapsed');\n // If there are many nodes, apply the following.\n if (content.childNodes.length > 1) {\n content.classList.add('d-flex');\n }\n nodeSet.forEach(node => {\n node?.classList.remove('d-none');\n });\n expandButton?.classList.add('d-none');\n cellMenuButton?.focus();\n } else {\n element.classList.add('collapsed');\n content.classList.remove('d-flex');\n nodeSet.forEach(node => {\n node?.classList.add('d-none');\n });\n expandButton?.classList.remove('d-none');\n }\n }\n }\n\n /**\n * Update the visual count of collapsed columns or hide the count all together.\n */\n countUpdate() {\n countIndicator.textContent = this.getDatasetSize();\n if (this.getDatasetSize() > 0) {\n this.component.parentElement.classList.add('d-flex');\n this.component.parentElement.classList.remove('d-none');\n } else {\n this.component.parentElement.classList.remove('d-flex');\n this.component.parentElement.classList.add('d-none');\n }\n }\n\n /**\n * Build the content then replace the node by default we want our form to exist.\n */\n async renderDefault() {\n this.setMatchedResults(await this.filterDataset(this.getDataset()));\n this.filterMatchDataset();\n\n // Update the collapsed button pill.\n this.countUpdate();\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapsebody', {\n 'results': this.getMatchedResults(),\n 'userid': this.userID,\n });\n replaceNode(selectors.placeholder, html, js);\n this.updateNodes();\n\n // Given we now have the body, we can set up more triggers.\n this.registerFormEvents();\n this.registerInputEvents();\n\n // Add a small BS listener so that we can set the focus correctly on open.\n this.$component.on('shown.bs.dropdown', () => {\n this.searchInput.focus({preventScroll: true});\n });\n }\n\n /**\n * Build the content then replace the node.\n */\n async renderDropdown() {\n const form = this.component.querySelector(selectors.formDropdown);\n const selectall = form.querySelector('[data-action=\"selectall\"]');\n const {html, js} = await renderForPromise('gradereport_grader/collapse/collapseresults', {\n 'results': this.getMatchedResults(),\n 'searchTerm': this.getSearchTerm(),\n });\n selectall.disabled = this.getMatchedResults().length === 0;\n replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);\n }\n\n /**\n * If we have any custom user profile fields, grab their system & readable names to add to our string map.\n *\n * @returns {array} An array of associated string arrays ready for our map.\n */\n fetchCustomFieldValues() {\n const customFields = document.querySelectorAll('[data-collapse-name]');\n // Cast from NodeList to array to grab all the values.\n return [...customFields].map(field => [field.parentElement.dataset.col, field.dataset.collapseName]);\n }\n\n /**\n * Given the set of profile fields we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promise}\n */\n fetchRequiredUserStrings() {\n if (!this.userStrings) {\n const requiredStrings = [\n 'username',\n 'firstname',\n 'lastname',\n 'email',\n 'city',\n 'country',\n 'department',\n 'institution',\n 'idnumber',\n 'phone1',\n 'phone2',\n ];\n this.userStrings = getStrings(requiredStrings.map((key) => ({key})))\n .then((stringArray) => new Map(\n requiredStrings.map((key, index) => ([key, stringArray[index]]))\n ));\n }\n return this.userStrings;\n }\n\n /**\n * Given the set of gradable items we can possibly search, fetch their strings,\n * so we can report to screen readers the field that matched.\n *\n * @returns {Promise}\n */\n fetchRequiredGradeStrings() {\n if (!this.gradeStrings) {\n this.gradeStrings = Repository.gradeItems(this.courseID)\n .then((result) => new Map(\n result.gradeItems.map(key => ([key.id, key]))\n ));\n }\n return this.gradeStrings;\n }\n}\n"],"names":["selectors","cancel","save","checked","currentlyUnchecked","countIndicator","document","querySelector","ColumnSearch","search_combobox","userID","courseID","defaultSort","constructor","component","pendingPromise","Pending","then","loader","setTimeout","getDataset","forEach","item","nodesUpdate","renderDefault","remove","classList","resolve","catch","Notification","exception","componentSelector","dropdownSelector","triggerSelector","this","dataset","cols","fetchDataset","JSON","parse","split","datasetSize","length","storage","get","setPreferences","set","stringify","join","registerClickHandlers","addEventListener","clickHandler","bind","docClickHandler","e","target","closest","stopPropagation","hider","preventDefault","desiredToHide","_e$target$closest","col","_e$target$closest2","itemid","indexOf","push","prefcountpipe","_e$target$closest4","_e$target$closest5","idx","splice","_e$target$closest6","_e$target$closest7","keyHandler","key","input","clearSearchButton","focus","preventScroll","registerInputEvents","searchInput","async","getSearchTerm","value","searchResultsVisible","window","console","warn","setSearchTerms","add","filterrenderpipe","pending","registerFormEvents","form","events","CustomEvents","activate","keyboardActivate","define","selectall","event","submitBtn","checkedCount","Array","from","querySelectorAll","disabled","submitter","action","dropdown","elements","filter","collapse","updateDisplay","countUpdate","filterableData","stringUserMap","fetchRequiredUserStrings","stringGradeMap","fetchRequiredGradeStrings","customFieldMap","fetchCustomFieldValues","stringMap","Map","searching","map","s","mapObj","undefined","string","itemname","category","getPreppedSearchTerm","toString","toLowerCase","includes","filterMatchDataset","setMatchedResults","getMatchedResults","column","name","displayName","promises","element","updateDisplayForElement","Promise","all","content","sort","expandButton","rangeRowCell","avgRowCell","cellMenuButton","nodeSet","contains","location","rowCell","toggle","childNodes","node","textContent","getDatasetSize","parentElement","filterDataset","html","js","updateNodes","$component","on","getHTMLElements","searchDropdown","field","collapseName","userStrings","requiredStrings","stringArray","index","gradeStrings","Repository","gradeItems","result","id"],"mappings":"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"} \ No newline at end of file diff --git a/grade/report/grader/amd/src/collapse.js b/grade/report/grader/amd/src/collapse.js index c24997d70b8..12d10a0d3c7 100644 --- a/grade/report/grader/amd/src/collapse.js +++ b/grade/report/grader/amd/src/collapse.js @@ -56,6 +56,7 @@ const selectors = { count: '[data-collapse="count"]', placeholder: '.collapsecolumndropdown [data-region="placeholder"]', fullDropdown: '.collapsecolumndropdown', + cellMenuButton: '.cellmenubtn', }; const countIndicator = document.querySelector(selectors.count); @@ -191,6 +192,7 @@ export default class ColumnSearch extends search_combobox { async docClickHandler(e) { if (e.target.dataset.hider === selectors.hider) { e.preventDefault(); + const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:hide'); const desiredToHide = e.target.closest(selectors.colVal) ? e.target.closest(selectors.colVal)?.dataset.col : e.target.closest(selectors.itemVal)?.dataset.itemid; @@ -200,11 +202,13 @@ export default class ColumnSearch extends search_combobox { } await this.prefcountpipe(); - this.nodesUpdate(desiredToHide); + await this.nodesUpdate(desiredToHide); + pendingPromise.resolve(); } if (e.target.closest('button')?.dataset.hider === selectors.expand) { e.preventDefault(); + const pendingPromise = new Pending('gradereport_grader/collapse:docClickHandler:expand'); const desiredToHide = e.target.closest(selectors.colVal) ? e.target.closest(selectors.colVal)?.dataset.col : e.target.closest(selectors.itemVal)?.dataset.itemid; @@ -213,8 +217,9 @@ export default class ColumnSearch extends search_combobox { await this.prefcountpipe(); - this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col); - this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid); + await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col); + await this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid); + pendingPromise.resolve(); } } @@ -345,11 +350,14 @@ export default class ColumnSearch extends search_combobox { }); } - nodesUpdate(item) { + async nodesUpdate(item) { const colNodesToHide = [...document.querySelectorAll(`[data-col="${item}"]`)]; const itemIDNodesToHide = [...document.querySelectorAll(`[data-itemid="${item}"]`)]; - this.nodes = [...colNodesToHide, ...itemIDNodesToHide]; - this.updateDisplay(); + const elements = [...colNodesToHide, ...itemIDNodesToHide]; + if (elements && elements.length) { + const pendingPromise = new Pending('gradereport_grader/collapse:nodesUpdate:' + item); + this.updateDisplay(elements).then(() => pendingPromise.resolve()).catch(Notification.exception); + } } /** @@ -414,52 +422,68 @@ export default class ColumnSearch extends search_combobox { /** * With an array of nodes, switch their classes and values. + * + * @param {Array} elements The elements to update. */ - updateDisplay() { - this.nodes.forEach((element) => { - const content = element.querySelector(selectors.content); - const sort = element.querySelector(selectors.sort); - const expandButton = element.querySelector(selectors.expandbutton); - const rangeRowCell = element.querySelector(selectors.rangerowcell); - const avgRowCell = element.querySelector(selectors.avgrowcell); - const nodeSet = [ - element.querySelector(selectors.menu), - element.querySelector(selectors.icons), - content - ]; - - // This can be further improved to reduce redundant similar calls. - if (element.classList.contains('cell')) { - // The column is actively being sorted, lets reset that and reload the page. - if (sort !== null) { - window.location = this.defaultSort; - } - if (content === null) { - // If it's not a content cell, it must be an overall average or a range cell. - const rowCell = avgRowCell ?? rangeRowCell; - - rowCell?.classList.toggle('d-none'); - } else if (content.classList.contains('d-none')) { - // We should always have content but some cells do not contain menus or other actions. - element.classList.remove('collapsed'); - // If there are many nodes, apply the following. - if (content.childNodes.length > 1) { - content.classList.add('d-flex'); - } - nodeSet.forEach(node => { - node?.classList.remove('d-none'); - }); - expandButton?.classList.add('d-none'); - } else { - element.classList.add('collapsed'); - content.classList.remove('d-flex'); - nodeSet.forEach(node => { - node?.classList.add('d-none'); - }); - expandButton?.classList.remove('d-none'); - } - } + async updateDisplay(elements) { + const promises = []; + elements.forEach((element) => { + promises.push(this.updateDisplayForElement(element)); }); + + await Promise.all(promises); + } + + /** + * Update display for given element, switch its classes and values. + * + * @param {HTMLElement} element The element to update. + */ + async updateDisplayForElement(element) { + const content = element.querySelector(selectors.content); + const sort = element.querySelector(selectors.sort); + const expandButton = element.querySelector(selectors.expandbutton); + const rangeRowCell = element.querySelector(selectors.rangerowcell); + const avgRowCell = element.querySelector(selectors.avgrowcell); + const cellMenuButton = element.querySelector(selectors.cellMenuButton); + const nodeSet = [ + element.querySelector(selectors.menu), + element.querySelector(selectors.icons), + content + ]; + + // This can be further improved to reduce redundant similar calls. + if (element.classList.contains('cell')) { + // The column is actively being sorted, lets reset that and reload the page. + if (sort !== null) { + window.location = this.defaultSort; + } + if (content === null) { + // If it's not a content cell, it must be an overall average or a range cell. + const rowCell = avgRowCell ?? rangeRowCell; + + rowCell?.classList.toggle('d-none'); + } else if (content.classList.contains('d-none')) { + // We should always have content but some cells do not contain menus or other actions. + element.classList.remove('collapsed'); + // If there are many nodes, apply the following. + if (content.childNodes.length > 1) { + content.classList.add('d-flex'); + } + nodeSet.forEach(node => { + node?.classList.remove('d-none'); + }); + expandButton?.classList.add('d-none'); + cellMenuButton?.focus(); + } else { + element.classList.add('collapsed'); + content.classList.remove('d-flex'); + nodeSet.forEach(node => { + node?.classList.add('d-none'); + }); + expandButton?.classList.remove('d-none'); + } + } } /**