From 0063ed3e7621662a48b59437ccc78b627c3550ca Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Tue, 2 Nov 2021 11:03:58 +1100 Subject: [PATCH 1/2] MDL-72688 contentbank: Add and maintain appropriate aria roles Also moved header text into the sorting button as per the example at https://dequeuniversity.com/library/aria/table-sortable --- contentbank/amd/build/selectors.min.js | 2 +- contentbank/amd/build/selectors.min.js.map | 2 +- contentbank/amd/build/sort.min.js | 2 +- contentbank/amd/build/sort.min.js.map | 2 +- contentbank/amd/src/selectors.js | 2 + contentbank/amd/src/sort.js | 28 +++++++++-- contentbank/templates/bankcontent.mustache | 55 +++++++++++----------- theme/boost/scss/moodle/contentbank.scss | 5 +- theme/boost/style/moodle.css | 5 +- theme/classic/style/moodle.css | 5 +- 10 files changed, 70 insertions(+), 38 deletions(-) diff --git a/contentbank/amd/build/selectors.min.js b/contentbank/amd/build/selectors.min.js index 46d90ce697f..933ee3057e1 100644 --- a/contentbank/amd/build/selectors.min.js +++ b/contentbank/amd/build/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{cbcontentname:b("region","cb-content-name"),contentbank:b("region","contentbank"),filearea:b("region","filearea")},actions:{search:b("action","searchcontent"),clearSearch:b("action","clearsearch"),viewgrid:b("action","viewgrid"),viewlist:b("action","viewlist"),sortname:b("action","sortname"),sortuses:b("action","sortuses"),sortdate:b("action","sortdate"),sortsize:b("action","sortsize"),sorttype:b("action","sorttype"),sortauthor:b("action","sortauthor")},elements:{listitem:".cb-listitem",cbnavbarbreadcrumb:".cb-navbar-breadbrumb",cbnavbartotalsearch:".cb-navbar-totalsearch",searchinput:"#searchinput",sortbutton:".cb-btnsort"}};a.default=c;return a.default}); +define ("core_contentbank/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{cbcontentname:b("region","cb-content-name"),contentbank:b("region","contentbank"),filearea:b("region","filearea")},actions:{search:b("action","searchcontent"),clearSearch:b("action","clearsearch"),viewgrid:b("action","viewgrid"),viewlist:b("action","viewlist"),sortname:b("action","sortname"),sortuses:b("action","sortuses"),sortdate:b("action","sortdate"),sortsize:b("action","sortsize"),sorttype:b("action","sorttype"),sortauthor:b("action","sortauthor")},elements:{listitem:".cb-listitem",heading:".cb-heading",cell:".cb-column",cbnavbarbreadcrumb:".cb-navbar-breadbrumb",cbnavbartotalsearch:".cb-navbar-totalsearch",searchinput:"#searchinput",sortbutton:".cb-btnsort"}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/contentbank/amd/build/selectors.min.js.map b/contentbank/amd/build/selectors.min.js.map index 25589f2187c..fd743ed665d 100644 --- a/contentbank/amd/build/selectors.min.js.map +++ b/contentbank/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/selectors.js"],"names":["getDataSelector","name","value","regions","cbcontentname","contentbank","filearea","actions","search","clearSearch","viewgrid","viewlist","sortname","sortuses","sortdate","sortsize","sorttype","sortauthor","elements","listitem","cbnavbarbreadcrumb","cbnavbartotalsearch","searchinput","sortbutton"],"mappings":"+IA+BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,aAAa,CAAEJ,CAAe,CAAC,QAAD,CAAW,iBAAX,CADzB,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,QAAQ,CAAEN,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CADE,CAMXO,OAAO,CAAE,CACLC,MAAM,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CADlB,CAELS,WAAW,CAAET,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLU,QAAQ,CAAEV,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CAILW,QAAQ,CAAEX,CAAe,CAAC,QAAD,CAAW,UAAX,CAJpB,CAKLY,QAAQ,CAAEZ,CAAe,CAAC,QAAD,CAAW,UAAX,CALpB,CAMLa,QAAQ,CAAEb,CAAe,CAAC,QAAD,CAAW,UAAX,CANpB,CAOLc,QAAQ,CAAEd,CAAe,CAAC,QAAD,CAAW,UAAX,CAPpB,CAQLe,QAAQ,CAAEf,CAAe,CAAC,QAAD,CAAW,UAAX,CARpB,CASLgB,QAAQ,CAAEhB,CAAe,CAAC,QAAD,CAAW,UAAX,CATpB,CAULiB,UAAU,CAAEjB,CAAe,CAAC,QAAD,CAAW,YAAX,CAVtB,CANE,CAkBXkB,QAAQ,CAAE,CACNC,QAAQ,CAAE,cADJ,CAENC,kBAAkB,CAAE,uBAFd,CAGNC,mBAAmB,CAAE,wBAHf,CAINC,WAAW,CAAE,cAJP,CAKNC,UAAU,CAAE,aALN,CAlBC,C","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 * Define all of the selectors we will be using on the contentbank interface.\n *\n * @module core_contentbank/selectors\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n *\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n cbcontentname: getDataSelector('region', 'cb-content-name'),\n contentbank: getDataSelector('region', 'contentbank'),\n filearea: getDataSelector('region', 'filearea')\n },\n actions: {\n search: getDataSelector('action', 'searchcontent'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n viewgrid: getDataSelector('action', 'viewgrid'),\n viewlist: getDataSelector('action', 'viewlist'),\n sortname: getDataSelector('action', 'sortname'),\n sortuses: getDataSelector('action', 'sortuses'),\n sortdate: getDataSelector('action', 'sortdate'),\n sortsize: getDataSelector('action', 'sortsize'),\n sorttype: getDataSelector('action', 'sorttype'),\n sortauthor: getDataSelector('action', 'sortauthor'),\n },\n elements: {\n listitem: '.cb-listitem',\n cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',\n cbnavbartotalsearch: '.cb-navbar-totalsearch',\n searchinput: '#searchinput',\n sortbutton: '.cb-btnsort'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/selectors.js"],"names":["getDataSelector","name","value","regions","cbcontentname","contentbank","filearea","actions","search","clearSearch","viewgrid","viewlist","sortname","sortuses","sortdate","sortsize","sorttype","sortauthor","elements","listitem","heading","cell","cbnavbarbreadcrumb","cbnavbartotalsearch","searchinput","sortbutton"],"mappings":"+IA+BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,aAAa,CAAEJ,CAAe,CAAC,QAAD,CAAW,iBAAX,CADzB,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,QAAQ,CAAEN,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CADE,CAMXO,OAAO,CAAE,CACLC,MAAM,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CADlB,CAELS,WAAW,CAAET,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLU,QAAQ,CAAEV,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CAILW,QAAQ,CAAEX,CAAe,CAAC,QAAD,CAAW,UAAX,CAJpB,CAKLY,QAAQ,CAAEZ,CAAe,CAAC,QAAD,CAAW,UAAX,CALpB,CAMLa,QAAQ,CAAEb,CAAe,CAAC,QAAD,CAAW,UAAX,CANpB,CAOLc,QAAQ,CAAEd,CAAe,CAAC,QAAD,CAAW,UAAX,CAPpB,CAQLe,QAAQ,CAAEf,CAAe,CAAC,QAAD,CAAW,UAAX,CARpB,CASLgB,QAAQ,CAAEhB,CAAe,CAAC,QAAD,CAAW,UAAX,CATpB,CAULiB,UAAU,CAAEjB,CAAe,CAAC,QAAD,CAAW,YAAX,CAVtB,CANE,CAkBXkB,QAAQ,CAAE,CACNC,QAAQ,CAAE,cADJ,CAENC,OAAO,CAAE,aAFH,CAGNC,IAAI,CAAE,YAHA,CAINC,kBAAkB,CAAE,uBAJd,CAKNC,mBAAmB,CAAE,wBALf,CAMNC,WAAW,CAAE,cANP,CAONC,UAAU,CAAE,aAPN,CAlBC,C","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 * Define all of the selectors we will be using on the contentbank interface.\n *\n * @module core_contentbank/selectors\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n *\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n cbcontentname: getDataSelector('region', 'cb-content-name'),\n contentbank: getDataSelector('region', 'contentbank'),\n filearea: getDataSelector('region', 'filearea')\n },\n actions: {\n search: getDataSelector('action', 'searchcontent'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n viewgrid: getDataSelector('action', 'viewgrid'),\n viewlist: getDataSelector('action', 'viewlist'),\n sortname: getDataSelector('action', 'sortname'),\n sortuses: getDataSelector('action', 'sortuses'),\n sortdate: getDataSelector('action', 'sortdate'),\n sortsize: getDataSelector('action', 'sortsize'),\n sorttype: getDataSelector('action', 'sorttype'),\n sortauthor: getDataSelector('action', 'sortauthor'),\n },\n elements: {\n listitem: '.cb-listitem',\n heading: '.cb-heading',\n cell: '.cb-column',\n cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',\n cbnavbartotalsearch: '.cb-navbar-totalsearch',\n searchinput: '#searchinput',\n sortbutton: '.cb-btnsort'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/contentbank/amd/build/sort.min.js b/contentbank/amd/build/sort.min.js index dcbccf7f874..55e8c38a7fe 100644 --- a/contentbank/amd/build/sort.min.js +++ b/contentbank/amd/build/sort.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/sort",["exports","./selectors","core/str","core/prefetch","core/ajax","core/notification"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=g(b);d=g(d);e=g(e);f=g(f);function g(a){return a&&a.__esModule?a:{default:a}}var h=function(){var a=document.querySelector(b.default.regions.contentbank);d.default.prefetchStrings("contentbank",["contentname","uses","lastmodified","size","type","author"]);d.default.prefetchStrings("moodle",["sortbyx","sortbyxreverse"]);i(a)};a.init=h;var i=function(a){a.addEventListener("click",function(c){var d=a.querySelector(b.default.actions.viewlist),e=a.querySelector(b.default.actions.viewgrid);if(c.target.closest(b.default.actions.viewgrid)){a.classList.remove("view-list");a.classList.add("view-grid");e.classList.add("active");d.classList.remove("active");j(!1);return}if(c.target.closest(b.default.actions.viewlist)){a.classList.remove("view-grid");a.classList.add("view-list");d.classList.add("active");e.classList.remove("active");j(!0);return}var f=document.querySelector(b.default.regions.filearea),g=f.querySelectorAll(b.default.elements.listitem);if(f&&g){var h=c.target.closest(b.default.actions.sortname);if(h){var i=k(a,h);m(f,g,"data-file",i);return}var l=c.target.closest(b.default.actions.sortuses);if(l){var n=k(a,l);m(f,g,"data-uses",n);return}var o=c.target.closest(b.default.actions.sortdate);if(o){var p=k(a,o);m(f,g,"data-timemodified",p);return}var q=c.target.closest(b.default.actions.sortsize);if(q){var r=k(a,q);m(f,g,"data-bytes",r);return}var s=c.target.closest(b.default.actions.sorttype);if(s){var t=k(a,s);m(f,g,"data-type",t);return}var u=c.target.closest(b.default.actions.sortauthor);if(u){var v=k(a,u);m(f,g,"data-author",v)}}})},j=function(a){if(!1===a){a=null}var b={methodname:"core_user_update_user_preferences",args:{preferences:[{type:"core_contentbank_view_list",value:a}]}};return e.default.call([b])[0].catch(f.default.exception)},k=function(a,c){var d=a.querySelectorAll(b.default.elements.sortbutton);d.forEach(function(a){if(a!==c){a.classList.remove("dir-asc");a.classList.remove("dir-desc");a.classList.add("dir-none");l(a,!1)}});var e=!0;if(c.classList.contains("dir-none")){c.classList.remove("dir-none");c.classList.add("dir-asc")}else if(c.classList.contains("dir-asc")){c.classList.remove("dir-asc");c.classList.add("dir-desc");e=!1}else if(c.classList.contains("dir-desc")){c.classList.remove("dir-desc");c.classList.add("dir-asc")}l(c,e);return e},l=function(a,b){var d=b?"sortbyxreverse":"sortbyx";return(0,c.get_string)(a.dataset.string,"contentbank").then(function(a){return(0,c.get_string)(d,"core",a)}).then(function(b){a.setAttribute("title",b);return b}).catch()},m=function(a,b,c,d){var e=[].slice.call(b).sort(function(e,a){var b=e.getAttribute(c),f=a.getAttribute(c);if(!isNaN(b)){b=parseInt(b);f=parseInt(f)}if(d){return b>f?1:-1}else{return bf?1:-1}else{return b.\n\n/**\n * Content bank UI actions.\n *\n * @module core_contentbank/sort\n * @copyright 2020 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport selectors from './selectors';\nimport {get_string as getString} from 'core/str';\nimport Prefetch from 'core/prefetch';\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Set up the contentbank views.\n *\n * @method init\n */\nexport const init = () => {\n const contentBank = document.querySelector(selectors.regions.contentbank);\n Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);\n Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);\n registerListenerEvents(contentBank);\n};\n\n/**\n * Register contentbank related event listeners.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} contentBank The DOM node of the content bank\n */\nconst registerListenerEvents = (contentBank) => {\n\n contentBank.addEventListener('click', e => {\n const viewList = contentBank.querySelector(selectors.actions.viewlist);\n const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);\n\n // View as Grid button.\n if (e.target.closest(selectors.actions.viewgrid)) {\n contentBank.classList.remove('view-list');\n contentBank.classList.add('view-grid');\n viewGrid.classList.add('active');\n viewList.classList.remove('active');\n setViewListPreference(false);\n\n return;\n }\n\n // View as List button.\n if (e.target.closest(selectors.actions.viewlist)) {\n contentBank.classList.remove('view-grid');\n contentBank.classList.add('view-list');\n viewList.classList.add('active');\n viewGrid.classList.remove('active');\n setViewListPreference(true);\n\n return;\n }\n\n // TODO: This should _not_ use `document`. Every query should be constrained to the content bank container.\n const fileArea = document.querySelector(selectors.regions.filearea);\n const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);\n\n if (fileArea && shownItems) {\n\n // Sort by file name alphabetical\n const sortByName = e.target.closest(selectors.actions.sortname);\n if (sortByName) {\n const ascending = updateSortButtons(contentBank, sortByName);\n updateSortOrder(fileArea, shownItems, 'data-file', ascending);\n return;\n }\n\n // Sort by uses.\n const sortByUses = e.target.closest(selectors.actions.sortuses);\n if (sortByUses) {\n const ascending = updateSortButtons(contentBank, sortByUses);\n updateSortOrder(fileArea, shownItems, 'data-uses', ascending);\n return;\n }\n\n // Sort by date.\n const sortByDate = e.target.closest(selectors.actions.sortdate);\n if (sortByDate) {\n const ascending = updateSortButtons(contentBank, sortByDate);\n updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);\n return;\n }\n\n // Sort by size.\n const sortBySize = e.target.closest(selectors.actions.sortsize);\n if (sortBySize) {\n const ascending = updateSortButtons(contentBank, sortBySize);\n updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);\n return;\n }\n\n // Sort by type.\n const sortByType = e.target.closest(selectors.actions.sorttype);\n if (sortByType) {\n const ascending = updateSortButtons(contentBank, sortByType);\n updateSortOrder(fileArea, shownItems, 'data-type', ascending);\n return;\n }\n\n // Sort by author.\n const sortByAuthor = e.target.closest(selectors.actions.sortauthor);\n if (sortByAuthor) {\n const ascending = updateSortButtons(contentBank, sortByAuthor);\n updateSortOrder(fileArea, shownItems, 'data-author', ascending);\n }\n return;\n }\n });\n};\n\n\n/**\n * Set the contentbank user preference in list view\n *\n * @param {Bool} viewList view ContentBank as list.\n * @return {Promise} Repository promise.\n */\nconst setViewListPreference = function(viewList) {\n\n // If the given status is not hidden, the preference has to be deleted with a null value.\n if (viewList === false) {\n viewList = null;\n }\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: 'core_contentbank_view_list',\n value: viewList\n }\n ]\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n\n/**\n * Update the sort button view.\n *\n * @method updateSortButtons\n * @param {HTMLElement} contentBank The DOM node of the contentbank button\n * @param {HTMLElement} sortButton The DOM node of the sort button\n * @return {Bool} sort ascending\n */\nconst updateSortButtons = (contentBank, sortButton) => {\n const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);\n\n sortButtons.forEach((button) => {\n if (button !== sortButton) {\n button.classList.remove('dir-asc');\n button.classList.remove('dir-desc');\n button.classList.add('dir-none');\n\n updateButtonTitle(button, false);\n }\n });\n\n let ascending = true;\n\n if (sortButton.classList.contains('dir-none')) {\n sortButton.classList.remove('dir-none');\n sortButton.classList.add('dir-asc');\n } else if (sortButton.classList.contains('dir-asc')) {\n sortButton.classList.remove('dir-asc');\n sortButton.classList.add('dir-desc');\n ascending = false;\n } else if (sortButton.classList.contains('dir-desc')) {\n sortButton.classList.remove('dir-desc');\n sortButton.classList.add('dir-asc');\n }\n\n updateButtonTitle(sortButton, ascending);\n\n return ascending;\n};\n\n/**\n * Update the button title.\n *\n * @method updateButtonTitle\n * @param {HTMLElement} button Button to update\n * @param {Bool} ascending Sort direction\n * @return {Promise} string promise\n */\nconst updateButtonTitle = (button, ascending) => {\n\n const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');\n\n return getString(button.dataset.string, 'contentbank')\n .then(columnName => {\n return getString(sortString, 'core', columnName);\n })\n .then(sortByString => {\n button.setAttribute('title', sortByString);\n return sortByString;\n })\n .catch();\n};\n\n/**\n * Update the sort order of the itemlist and update the DOM\n *\n * @method updateSortOrder\n * @param {HTMLElement} fileArea the Dom container for the itemlist\n * @param {Array} itemList Nodelist of Dom elements\n * @param {String} attribute the attribut to sort on\n * @param {Bool} ascending Sort Ascending\n */\nconst updateSortOrder = (fileArea, itemList, attribute, ascending) => {\n const sortList = [].slice.call(itemList).sort(function(a, b) {\n\n let aa = a.getAttribute(attribute);\n let bb = b.getAttribute(attribute);\n if (!isNaN(aa)) {\n aa = parseInt(aa);\n bb = parseInt(bb);\n }\n\n if (ascending) {\n return aa > bb ? 1 : -1;\n } else {\n return aa < bb ? 1 : -1;\n }\n });\n sortList.forEach(listItem => fileArea.appendChild(listItem));\n};\n"],"file":"sort.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/sort.js"],"names":["init","contentBank","document","querySelector","selectors","regions","contentbank","Prefetch","prefetchStrings","registerListenerEvents","addEventListener","e","viewList","actions","viewlist","viewGrid","viewgrid","fileArea","filearea","shownItems","querySelectorAll","elements","listitem","target","closest","classList","remove","add","setAttribute","forEach","listItem","cell","removeAttribute","heading","setViewListPreference","sortByName","sortname","ascending","updateSortButtons","updateSortOrder","sortByUses","sortuses","sortByDate","sortdate","sortBySize","sortsize","sortByType","sorttype","sortByAuthor","sortauthor","request","methodname","args","preferences","type","value","Ajax","call","catch","Notification","exception","sortButton","sortButtons","sortbutton","button","updateButtonTitle","contains","sortString","dataset","string","then","columnName","sortByString","itemList","attribute","sortList","slice","sort","a","b","aa","getAttribute","bb","isNaN","parseInt","appendChild"],"mappings":"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","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 * Content bank UI actions.\n *\n * @module core_contentbank/sort\n * @copyright 2020 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport selectors from './selectors';\nimport {get_string as getString} from 'core/str';\nimport Prefetch from 'core/prefetch';\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Set up the contentbank views.\n *\n * @method init\n */\nexport const init = () => {\n const contentBank = document.querySelector(selectors.regions.contentbank);\n Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);\n Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);\n registerListenerEvents(contentBank);\n};\n\n/**\n * Register contentbank related event listeners.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} contentBank The DOM node of the content bank\n */\nconst registerListenerEvents = (contentBank) => {\n\n contentBank.addEventListener('click', e => {\n const viewList = contentBank.querySelector(selectors.actions.viewlist);\n const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);\n const fileArea = contentBank.querySelector(selectors.regions.filearea);\n const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);\n\n // View as Grid button.\n if (e.target.closest(selectors.actions.viewgrid)) {\n contentBank.classList.remove('view-list');\n contentBank.classList.add('view-grid');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'list');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'listitem');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.removeAttribute('role');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n }\n viewGrid.classList.add('active');\n viewList.classList.remove('active');\n setViewListPreference(false);\n\n return;\n }\n\n // View as List button.\n if (e.target.closest(selectors.actions.viewlist)) {\n contentBank.classList.remove('view-grid');\n contentBank.classList.add('view-list');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'table');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'row');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.setAttribute('role', 'row');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader'));\n }\n viewList.classList.add('active');\n viewGrid.classList.remove('active');\n setViewListPreference(true);\n\n return;\n }\n\n if (fileArea && shownItems) {\n\n // Sort by file name alphabetical\n const sortByName = e.target.closest(selectors.actions.sortname);\n if (sortByName) {\n const ascending = updateSortButtons(contentBank, sortByName);\n updateSortOrder(fileArea, shownItems, 'data-file', ascending);\n return;\n }\n\n // Sort by uses.\n const sortByUses = e.target.closest(selectors.actions.sortuses);\n if (sortByUses) {\n const ascending = updateSortButtons(contentBank, sortByUses);\n updateSortOrder(fileArea, shownItems, 'data-uses', ascending);\n return;\n }\n\n // Sort by date.\n const sortByDate = e.target.closest(selectors.actions.sortdate);\n if (sortByDate) {\n const ascending = updateSortButtons(contentBank, sortByDate);\n updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);\n return;\n }\n\n // Sort by size.\n const sortBySize = e.target.closest(selectors.actions.sortsize);\n if (sortBySize) {\n const ascending = updateSortButtons(contentBank, sortBySize);\n updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);\n return;\n }\n\n // Sort by type.\n const sortByType = e.target.closest(selectors.actions.sorttype);\n if (sortByType) {\n const ascending = updateSortButtons(contentBank, sortByType);\n updateSortOrder(fileArea, shownItems, 'data-type', ascending);\n return;\n }\n\n // Sort by author.\n const sortByAuthor = e.target.closest(selectors.actions.sortauthor);\n if (sortByAuthor) {\n const ascending = updateSortButtons(contentBank, sortByAuthor);\n updateSortOrder(fileArea, shownItems, 'data-author', ascending);\n }\n return;\n }\n });\n};\n\n\n/**\n * Set the contentbank user preference in list view\n *\n * @param {Bool} viewList view ContentBank as list.\n * @return {Promise} Repository promise.\n */\nconst setViewListPreference = function(viewList) {\n\n // If the given status is not hidden, the preference has to be deleted with a null value.\n if (viewList === false) {\n viewList = null;\n }\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: 'core_contentbank_view_list',\n value: viewList\n }\n ]\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n\n/**\n * Update the sort button view.\n *\n * @method updateSortButtons\n * @param {HTMLElement} contentBank The DOM node of the contentbank button\n * @param {HTMLElement} sortButton The DOM node of the sort button\n * @return {Bool} sort ascending\n */\nconst updateSortButtons = (contentBank, sortButton) => {\n const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);\n\n sortButtons.forEach((button) => {\n if (button !== sortButton) {\n button.classList.remove('dir-asc');\n button.classList.remove('dir-desc');\n button.classList.add('dir-none');\n\n updateButtonTitle(button, false);\n }\n });\n\n let ascending = true;\n\n if (sortButton.classList.contains('dir-none')) {\n sortButton.classList.remove('dir-none');\n sortButton.classList.add('dir-asc');\n } else if (sortButton.classList.contains('dir-asc')) {\n sortButton.classList.remove('dir-asc');\n sortButton.classList.add('dir-desc');\n ascending = false;\n } else if (sortButton.classList.contains('dir-desc')) {\n sortButton.classList.remove('dir-desc');\n sortButton.classList.add('dir-asc');\n }\n\n updateButtonTitle(sortButton, ascending);\n\n return ascending;\n};\n\n/**\n * Update the button title.\n *\n * @method updateButtonTitle\n * @param {HTMLElement} button Button to update\n * @param {Bool} ascending Sort direction\n * @return {Promise} string promise\n */\nconst updateButtonTitle = (button, ascending) => {\n\n const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');\n\n return getString(button.dataset.string, 'contentbank')\n .then(columnName => {\n return getString(sortString, 'core', columnName);\n })\n .then(sortByString => {\n button.setAttribute('title', sortByString);\n return sortByString;\n })\n .catch();\n};\n\n/**\n * Update the sort order of the itemlist and update the DOM\n *\n * @method updateSortOrder\n * @param {HTMLElement} fileArea the Dom container for the itemlist\n * @param {Array} itemList Nodelist of Dom elements\n * @param {String} attribute the attribut to sort on\n * @param {Bool} ascending Sort Ascending\n */\nconst updateSortOrder = (fileArea, itemList, attribute, ascending) => {\n const sortList = [].slice.call(itemList).sort(function(a, b) {\n\n let aa = a.getAttribute(attribute);\n let bb = b.getAttribute(attribute);\n if (!isNaN(aa)) {\n aa = parseInt(aa);\n bb = parseInt(bb);\n }\n\n if (ascending) {\n return aa > bb ? 1 : -1;\n } else {\n return aa < bb ? 1 : -1;\n }\n });\n sortList.forEach(listItem => fileArea.appendChild(listItem));\n};\n"],"file":"sort.min.js"} \ No newline at end of file diff --git a/contentbank/amd/src/selectors.js b/contentbank/amd/src/selectors.js index 812cb7d438e..0d234e5e863 100644 --- a/contentbank/amd/src/selectors.js +++ b/contentbank/amd/src/selectors.js @@ -53,6 +53,8 @@ export default { }, elements: { listitem: '.cb-listitem', + heading: '.cb-heading', + cell: '.cb-column', cbnavbarbreadcrumb: '.cb-navbar-breadbrumb', cbnavbartotalsearch: '.cb-navbar-totalsearch', searchinput: '#searchinput', diff --git a/contentbank/amd/src/sort.js b/contentbank/amd/src/sort.js index be349971593..9263d2b3b6a 100644 --- a/contentbank/amd/src/sort.js +++ b/contentbank/amd/src/sort.js @@ -50,11 +50,24 @@ const registerListenerEvents = (contentBank) => { contentBank.addEventListener('click', e => { const viewList = contentBank.querySelector(selectors.actions.viewlist); const viewGrid = contentBank.querySelector(selectors.actions.viewgrid); + const fileArea = contentBank.querySelector(selectors.regions.filearea); + const shownItems = fileArea.querySelectorAll(selectors.elements.listitem); // View as Grid button. if (e.target.closest(selectors.actions.viewgrid)) { contentBank.classList.remove('view-list'); contentBank.classList.add('view-grid'); + if (fileArea && shownItems) { + fileArea.setAttribute('role', 'list'); + shownItems.forEach(listItem => { + listItem.setAttribute('role', 'listitem'); + listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role')); + }); + + const heading = fileArea.querySelector(selectors.elements.heading); + heading.removeAttribute('role'); + heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role')); + } viewGrid.classList.add('active'); viewList.classList.remove('active'); setViewListPreference(false); @@ -66,6 +79,17 @@ const registerListenerEvents = (contentBank) => { if (e.target.closest(selectors.actions.viewlist)) { contentBank.classList.remove('view-grid'); contentBank.classList.add('view-list'); + if (fileArea && shownItems) { + fileArea.setAttribute('role', 'table'); + shownItems.forEach(listItem => { + listItem.setAttribute('role', 'row'); + listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell')); + }); + + const heading = fileArea.querySelector(selectors.elements.heading); + heading.setAttribute('role', 'row'); + heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader')); + } viewList.classList.add('active'); viewGrid.classList.remove('active'); setViewListPreference(true); @@ -73,10 +97,6 @@ const registerListenerEvents = (contentBank) => { return; } - // TODO: This should _not_ use `document`. Every query should be constrained to the content bank container. - const fileArea = document.querySelector(selectors.regions.filearea); - const shownItems = fileArea.querySelectorAll(selectors.elements.listitem); - if (fileArea && shownItems) { // Sort by file name alphabetical diff --git a/contentbank/templates/bankcontent.mustache b/contentbank/templates/bankcontent.mustache index c2a229fd150..a96c57c1e10 100644 --- a/contentbank/templates/bankcontent.mustache +++ b/contentbank/templates/bankcontent.mustache @@ -125,57 +125,57 @@ data-region="contentbank"> {{#contents.0}} -
-
-
-
{{#str}} contentname, contentbank {{/str}}
-
-
-
{{#str}} uses, contentbank {{/str}}
-
-
-
{{#str}} lastmodified, contentbank {{/str}}
-
-
-
{{#str}} size, contentbank {{/str}}
-
-
-
{{#str}} type, contentbank {{/str}}
-
-
-
{{#str}} author, contentbank {{/str}}
-
{{#contents}} -
data-timemodified="{{ timemodified }}" data-type="{{ type }}" data-author="{{ author }}"> -
+
@@ -201,19 +202,19 @@ data-region="contentbank">
-
+
{{ uses }}
-
+
{{#userdate}} {{ timemodified }}, {{#str}} strftimedatetimeshort, core_langconfig {{/str}} {{/userdate}}
-
+
{{ size }}
-
+
{{{ type }}}
-
+
{{{ author }}}
diff --git a/theme/boost/scss/moodle/contentbank.scss b/theme/boost/scss/moodle/contentbank.scss index 6a56e53d84a..626f921f383 100644 --- a/theme/boost/scss/moodle/contentbank.scss +++ b/theme/boost/scss/moodle/contentbank.scss @@ -149,10 +149,13 @@ span { display: none; } + .title { + display: inline; + } &.dir-none .default, &.dir-asc .asc, &.dir-desc .desc { - display: block; + display: inline; } } } diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 16ef241d01b..cb0224a33ce 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -13612,10 +13612,13 @@ table.calendartable caption { .content-bank-container.view-list .cb-btnsort span { display: none; } +.content-bank-container.view-list .cb-btnsort .title { + display: inline; } + .content-bank-container.view-list .cb-btnsort.dir-none .default, .content-bank-container.view-list .cb-btnsort.dir-asc .asc, .content-bank-container.view-list .cb-btnsort.dir-desc .desc { - display: block; } + display: inline; } .cb-toolbar-container .dropdown-scrollable { max-height: 190px; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index c2b816be0d6..ff89b884331 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -13612,10 +13612,13 @@ table.calendartable caption { .content-bank-container.view-list .cb-btnsort span { display: none; } +.content-bank-container.view-list .cb-btnsort .title { + display: inline; } + .content-bank-container.view-list .cb-btnsort.dir-none .default, .content-bank-container.view-list .cb-btnsort.dir-asc .asc, .content-bank-container.view-list .cb-btnsort.dir-desc .desc { - display: block; } + display: inline; } .cb-toolbar-container .dropdown-scrollable { max-height: 190px; From 399b2d47727fcc00d5a4bab8dc25b7addee0fe80 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Fri, 5 Nov 2021 11:15:59 +1100 Subject: [PATCH 2/2] MDL-72688 contentbank: Some accessibility improvements described below - Label the table/list with aria-label - Hide sort buttons from screen-readers and use aria-sort instead - Remove the redundant title from decorative thumbnails --- contentbank/amd/build/sort.min.js | 2 +- contentbank/amd/build/sort.min.js.map | 2 +- contentbank/amd/src/sort.js | 5 ++ contentbank/templates/bankcontent.mustache | 56 +++++++++++----------- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/contentbank/amd/build/sort.min.js b/contentbank/amd/build/sort.min.js index 55e8c38a7fe..6a84c9b320b 100644 --- a/contentbank/amd/build/sort.min.js +++ b/contentbank/amd/build/sort.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/sort",["exports","./selectors","core/str","core/prefetch","core/ajax","core/notification"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=g(b);d=g(d);e=g(e);f=g(f);function g(a){return a&&a.__esModule?a:{default:a}}var h=function(){var a=document.querySelector(b.default.regions.contentbank);d.default.prefetchStrings("contentbank",["contentname","uses","lastmodified","size","type","author"]);d.default.prefetchStrings("moodle",["sortbyx","sortbyxreverse"]);i(a)};a.init=h;var i=function(a){a.addEventListener("click",function(c){var d=a.querySelector(b.default.actions.viewlist),e=a.querySelector(b.default.actions.viewgrid),f=a.querySelector(b.default.regions.filearea),g=f.querySelectorAll(b.default.elements.listitem);if(c.target.closest(b.default.actions.viewgrid)){a.classList.remove("view-list");a.classList.add("view-grid");if(f&&g){f.setAttribute("role","list");g.forEach(function(a){a.setAttribute("role","listitem");a.querySelectorAll(b.default.elements.cell).forEach(function(a){return a.removeAttribute("role")})});var h=f.querySelector(b.default.elements.heading);h.removeAttribute("role");h.querySelectorAll(b.default.elements.cell).forEach(function(a){return a.removeAttribute("role")})}e.classList.add("active");d.classList.remove("active");j(!1);return}if(c.target.closest(b.default.actions.viewlist)){a.classList.remove("view-grid");a.classList.add("view-list");if(f&&g){f.setAttribute("role","table");g.forEach(function(a){a.setAttribute("role","row");a.querySelectorAll(b.default.elements.cell).forEach(function(a){return a.setAttribute("role","cell")})});var i=f.querySelector(b.default.elements.heading);i.setAttribute("role","row");i.querySelectorAll(b.default.elements.cell).forEach(function(a){return a.setAttribute("role","columnheader")})}d.classList.add("active");e.classList.remove("active");j(!0);return}if(f&&g){var l=c.target.closest(b.default.actions.sortname);if(l){var n=k(a,l);m(f,g,"data-file",n);return}var o=c.target.closest(b.default.actions.sortuses);if(o){var p=k(a,o);m(f,g,"data-uses",p);return}var q=c.target.closest(b.default.actions.sortdate);if(q){var r=k(a,q);m(f,g,"data-timemodified",r);return}var s=c.target.closest(b.default.actions.sortsize);if(s){var t=k(a,s);m(f,g,"data-bytes",t);return}var u=c.target.closest(b.default.actions.sorttype);if(u){var v=k(a,u);m(f,g,"data-type",v);return}var w=c.target.closest(b.default.actions.sortauthor);if(w){var x=k(a,w);m(f,g,"data-author",x)}}})},j=function(a){if(!1===a){a=null}var b={methodname:"core_user_update_user_preferences",args:{preferences:[{type:"core_contentbank_view_list",value:a}]}};return e.default.call([b])[0].catch(f.default.exception)},k=function(a,c){var d=a.querySelectorAll(b.default.elements.sortbutton);d.forEach(function(a){if(a!==c){a.classList.remove("dir-asc");a.classList.remove("dir-desc");a.classList.add("dir-none");l(a,!1)}});var e=!0;if(c.classList.contains("dir-none")){c.classList.remove("dir-none");c.classList.add("dir-asc")}else if(c.classList.contains("dir-asc")){c.classList.remove("dir-asc");c.classList.add("dir-desc");e=!1}else if(c.classList.contains("dir-desc")){c.classList.remove("dir-desc");c.classList.add("dir-asc")}l(c,e);return e},l=function(a,b){var d=b?"sortbyxreverse":"sortbyx";return(0,c.get_string)(a.dataset.string,"contentbank").then(function(a){return(0,c.get_string)(d,"core",a)}).then(function(b){a.setAttribute("title",b);return b}).catch()},m=function(a,b,c,d){var e=[].slice.call(b).sort(function(e,a){var b=e.getAttribute(c),f=a.getAttribute(c);if(!isNaN(b)){b=parseInt(b);f=parseInt(f)}if(d){return b>f?1:-1}else{return bf?1:-1}else{return b.\n\n/**\n * Content bank UI actions.\n *\n * @module core_contentbank/sort\n * @copyright 2020 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport selectors from './selectors';\nimport {get_string as getString} from 'core/str';\nimport Prefetch from 'core/prefetch';\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Set up the contentbank views.\n *\n * @method init\n */\nexport const init = () => {\n const contentBank = document.querySelector(selectors.regions.contentbank);\n Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);\n Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);\n registerListenerEvents(contentBank);\n};\n\n/**\n * Register contentbank related event listeners.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} contentBank The DOM node of the content bank\n */\nconst registerListenerEvents = (contentBank) => {\n\n contentBank.addEventListener('click', e => {\n const viewList = contentBank.querySelector(selectors.actions.viewlist);\n const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);\n const fileArea = contentBank.querySelector(selectors.regions.filearea);\n const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);\n\n // View as Grid button.\n if (e.target.closest(selectors.actions.viewgrid)) {\n contentBank.classList.remove('view-list');\n contentBank.classList.add('view-grid');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'list');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'listitem');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.removeAttribute('role');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n }\n viewGrid.classList.add('active');\n viewList.classList.remove('active');\n setViewListPreference(false);\n\n return;\n }\n\n // View as List button.\n if (e.target.closest(selectors.actions.viewlist)) {\n contentBank.classList.remove('view-grid');\n contentBank.classList.add('view-list');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'table');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'row');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.setAttribute('role', 'row');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader'));\n }\n viewList.classList.add('active');\n viewGrid.classList.remove('active');\n setViewListPreference(true);\n\n return;\n }\n\n if (fileArea && shownItems) {\n\n // Sort by file name alphabetical\n const sortByName = e.target.closest(selectors.actions.sortname);\n if (sortByName) {\n const ascending = updateSortButtons(contentBank, sortByName);\n updateSortOrder(fileArea, shownItems, 'data-file', ascending);\n return;\n }\n\n // Sort by uses.\n const sortByUses = e.target.closest(selectors.actions.sortuses);\n if (sortByUses) {\n const ascending = updateSortButtons(contentBank, sortByUses);\n updateSortOrder(fileArea, shownItems, 'data-uses', ascending);\n return;\n }\n\n // Sort by date.\n const sortByDate = e.target.closest(selectors.actions.sortdate);\n if (sortByDate) {\n const ascending = updateSortButtons(contentBank, sortByDate);\n updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);\n return;\n }\n\n // Sort by size.\n const sortBySize = e.target.closest(selectors.actions.sortsize);\n if (sortBySize) {\n const ascending = updateSortButtons(contentBank, sortBySize);\n updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);\n return;\n }\n\n // Sort by type.\n const sortByType = e.target.closest(selectors.actions.sorttype);\n if (sortByType) {\n const ascending = updateSortButtons(contentBank, sortByType);\n updateSortOrder(fileArea, shownItems, 'data-type', ascending);\n return;\n }\n\n // Sort by author.\n const sortByAuthor = e.target.closest(selectors.actions.sortauthor);\n if (sortByAuthor) {\n const ascending = updateSortButtons(contentBank, sortByAuthor);\n updateSortOrder(fileArea, shownItems, 'data-author', ascending);\n }\n return;\n }\n });\n};\n\n\n/**\n * Set the contentbank user preference in list view\n *\n * @param {Bool} viewList view ContentBank as list.\n * @return {Promise} Repository promise.\n */\nconst setViewListPreference = function(viewList) {\n\n // If the given status is not hidden, the preference has to be deleted with a null value.\n if (viewList === false) {\n viewList = null;\n }\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: 'core_contentbank_view_list',\n value: viewList\n }\n ]\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n\n/**\n * Update the sort button view.\n *\n * @method updateSortButtons\n * @param {HTMLElement} contentBank The DOM node of the contentbank button\n * @param {HTMLElement} sortButton The DOM node of the sort button\n * @return {Bool} sort ascending\n */\nconst updateSortButtons = (contentBank, sortButton) => {\n const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);\n\n sortButtons.forEach((button) => {\n if (button !== sortButton) {\n button.classList.remove('dir-asc');\n button.classList.remove('dir-desc');\n button.classList.add('dir-none');\n\n updateButtonTitle(button, false);\n }\n });\n\n let ascending = true;\n\n if (sortButton.classList.contains('dir-none')) {\n sortButton.classList.remove('dir-none');\n sortButton.classList.add('dir-asc');\n } else if (sortButton.classList.contains('dir-asc')) {\n sortButton.classList.remove('dir-asc');\n sortButton.classList.add('dir-desc');\n ascending = false;\n } else if (sortButton.classList.contains('dir-desc')) {\n sortButton.classList.remove('dir-desc');\n sortButton.classList.add('dir-asc');\n }\n\n updateButtonTitle(sortButton, ascending);\n\n return ascending;\n};\n\n/**\n * Update the button title.\n *\n * @method updateButtonTitle\n * @param {HTMLElement} button Button to update\n * @param {Bool} ascending Sort direction\n * @return {Promise} string promise\n */\nconst updateButtonTitle = (button, ascending) => {\n\n const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');\n\n return getString(button.dataset.string, 'contentbank')\n .then(columnName => {\n return getString(sortString, 'core', columnName);\n })\n .then(sortByString => {\n button.setAttribute('title', sortByString);\n return sortByString;\n })\n .catch();\n};\n\n/**\n * Update the sort order of the itemlist and update the DOM\n *\n * @method updateSortOrder\n * @param {HTMLElement} fileArea the Dom container for the itemlist\n * @param {Array} itemList Nodelist of Dom elements\n * @param {String} attribute the attribut to sort on\n * @param {Bool} ascending Sort Ascending\n */\nconst updateSortOrder = (fileArea, itemList, attribute, ascending) => {\n const sortList = [].slice.call(itemList).sort(function(a, b) {\n\n let aa = a.getAttribute(attribute);\n let bb = b.getAttribute(attribute);\n if (!isNaN(aa)) {\n aa = parseInt(aa);\n bb = parseInt(bb);\n }\n\n if (ascending) {\n return aa > bb ? 1 : -1;\n } else {\n return aa < bb ? 1 : -1;\n }\n });\n sortList.forEach(listItem => fileArea.appendChild(listItem));\n};\n"],"file":"sort.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/sort.js"],"names":["init","contentBank","document","querySelector","selectors","regions","contentbank","Prefetch","prefetchStrings","registerListenerEvents","addEventListener","e","viewList","actions","viewlist","viewGrid","viewgrid","fileArea","filearea","shownItems","querySelectorAll","elements","listitem","target","closest","classList","remove","add","setAttribute","forEach","listItem","cell","removeAttribute","heading","setViewListPreference","sortByName","sortname","ascending","updateSortButtons","updateSortOrder","sortByUses","sortuses","sortByDate","sortdate","sortBySize","sortsize","sortByType","sorttype","sortByAuthor","sortauthor","request","methodname","args","preferences","type","value","Ajax","call","catch","Notification","exception","sortButton","sortButtons","sortbutton","button","updateButtonTitle","contains","sortString","dataset","string","then","columnName","sortByString","itemList","attribute","sortList","slice","sort","a","b","aa","getAttribute","bb","isNaN","parseInt","appendChild"],"mappings":"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","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 * Content bank UI actions.\n *\n * @module core_contentbank/sort\n * @copyright 2020 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport selectors from './selectors';\nimport {get_string as getString} from 'core/str';\nimport Prefetch from 'core/prefetch';\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Set up the contentbank views.\n *\n * @method init\n */\nexport const init = () => {\n const contentBank = document.querySelector(selectors.regions.contentbank);\n Prefetch.prefetchStrings('contentbank', ['contentname', 'uses', 'lastmodified', 'size', 'type', 'author']);\n Prefetch.prefetchStrings('moodle', ['sortbyx', 'sortbyxreverse']);\n registerListenerEvents(contentBank);\n};\n\n/**\n * Register contentbank related event listeners.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} contentBank The DOM node of the content bank\n */\nconst registerListenerEvents = (contentBank) => {\n\n contentBank.addEventListener('click', e => {\n const viewList = contentBank.querySelector(selectors.actions.viewlist);\n const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);\n const fileArea = contentBank.querySelector(selectors.regions.filearea);\n const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);\n\n // View as Grid button.\n if (e.target.closest(selectors.actions.viewgrid)) {\n contentBank.classList.remove('view-list');\n contentBank.classList.add('view-grid');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'list');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'listitem');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.removeAttribute('role');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.removeAttribute('role'));\n }\n viewGrid.classList.add('active');\n viewList.classList.remove('active');\n setViewListPreference(false);\n\n return;\n }\n\n // View as List button.\n if (e.target.closest(selectors.actions.viewlist)) {\n contentBank.classList.remove('view-grid');\n contentBank.classList.add('view-list');\n if (fileArea && shownItems) {\n fileArea.setAttribute('role', 'table');\n shownItems.forEach(listItem => {\n listItem.setAttribute('role', 'row');\n listItem.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'cell'));\n });\n\n const heading = fileArea.querySelector(selectors.elements.heading);\n heading.setAttribute('role', 'row');\n heading.querySelectorAll(selectors.elements.cell).forEach(cell => cell.setAttribute('role', 'columnheader'));\n }\n viewList.classList.add('active');\n viewGrid.classList.remove('active');\n setViewListPreference(true);\n\n return;\n }\n\n if (fileArea && shownItems) {\n\n // Sort by file name alphabetical\n const sortByName = e.target.closest(selectors.actions.sortname);\n if (sortByName) {\n const ascending = updateSortButtons(contentBank, sortByName);\n updateSortOrder(fileArea, shownItems, 'data-file', ascending);\n return;\n }\n\n // Sort by uses.\n const sortByUses = e.target.closest(selectors.actions.sortuses);\n if (sortByUses) {\n const ascending = updateSortButtons(contentBank, sortByUses);\n updateSortOrder(fileArea, shownItems, 'data-uses', ascending);\n return;\n }\n\n // Sort by date.\n const sortByDate = e.target.closest(selectors.actions.sortdate);\n if (sortByDate) {\n const ascending = updateSortButtons(contentBank, sortByDate);\n updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);\n return;\n }\n\n // Sort by size.\n const sortBySize = e.target.closest(selectors.actions.sortsize);\n if (sortBySize) {\n const ascending = updateSortButtons(contentBank, sortBySize);\n updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);\n return;\n }\n\n // Sort by type.\n const sortByType = e.target.closest(selectors.actions.sorttype);\n if (sortByType) {\n const ascending = updateSortButtons(contentBank, sortByType);\n updateSortOrder(fileArea, shownItems, 'data-type', ascending);\n return;\n }\n\n // Sort by author.\n const sortByAuthor = e.target.closest(selectors.actions.sortauthor);\n if (sortByAuthor) {\n const ascending = updateSortButtons(contentBank, sortByAuthor);\n updateSortOrder(fileArea, shownItems, 'data-author', ascending);\n }\n return;\n }\n });\n};\n\n\n/**\n * Set the contentbank user preference in list view\n *\n * @param {Bool} viewList view ContentBank as list.\n * @return {Promise} Repository promise.\n */\nconst setViewListPreference = function(viewList) {\n\n // If the given status is not hidden, the preference has to be deleted with a null value.\n if (viewList === false) {\n viewList = null;\n }\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: 'core_contentbank_view_list',\n value: viewList\n }\n ]\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n\n/**\n * Update the sort button view.\n *\n * @method updateSortButtons\n * @param {HTMLElement} contentBank The DOM node of the contentbank button\n * @param {HTMLElement} sortButton The DOM node of the sort button\n * @return {Bool} sort ascending\n */\nconst updateSortButtons = (contentBank, sortButton) => {\n const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);\n\n sortButtons.forEach((button) => {\n if (button !== sortButton) {\n button.classList.remove('dir-asc');\n button.classList.remove('dir-desc');\n button.classList.add('dir-none');\n\n button.closest(selectors.elements.cell).setAttribute('aria-sort', 'none');\n\n updateButtonTitle(button, false);\n }\n });\n\n let ascending = true;\n\n if (sortButton.classList.contains('dir-none')) {\n sortButton.classList.remove('dir-none');\n sortButton.classList.add('dir-asc');\n sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');\n } else if (sortButton.classList.contains('dir-asc')) {\n sortButton.classList.remove('dir-asc');\n sortButton.classList.add('dir-desc');\n sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'descending');\n ascending = false;\n } else if (sortButton.classList.contains('dir-desc')) {\n sortButton.classList.remove('dir-desc');\n sortButton.classList.add('dir-asc');\n sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending');\n }\n\n updateButtonTitle(sortButton, ascending);\n\n return ascending;\n};\n\n/**\n * Update the button title.\n *\n * @method updateButtonTitle\n * @param {HTMLElement} button Button to update\n * @param {Bool} ascending Sort direction\n * @return {Promise} string promise\n */\nconst updateButtonTitle = (button, ascending) => {\n\n const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');\n\n return getString(button.dataset.string, 'contentbank')\n .then(columnName => {\n return getString(sortString, 'core', columnName);\n })\n .then(sortByString => {\n button.setAttribute('title', sortByString);\n return sortByString;\n })\n .catch();\n};\n\n/**\n * Update the sort order of the itemlist and update the DOM\n *\n * @method updateSortOrder\n * @param {HTMLElement} fileArea the Dom container for the itemlist\n * @param {Array} itemList Nodelist of Dom elements\n * @param {String} attribute the attribut to sort on\n * @param {Bool} ascending Sort Ascending\n */\nconst updateSortOrder = (fileArea, itemList, attribute, ascending) => {\n const sortList = [].slice.call(itemList).sort(function(a, b) {\n\n let aa = a.getAttribute(attribute);\n let bb = b.getAttribute(attribute);\n if (!isNaN(aa)) {\n aa = parseInt(aa);\n bb = parseInt(bb);\n }\n\n if (ascending) {\n return aa > bb ? 1 : -1;\n } else {\n return aa < bb ? 1 : -1;\n }\n });\n sortList.forEach(listItem => fileArea.appendChild(listItem));\n};\n"],"file":"sort.min.js"} \ No newline at end of file diff --git a/contentbank/amd/src/sort.js b/contentbank/amd/src/sort.js index 9263d2b3b6a..a2ee79fd738 100644 --- a/contentbank/amd/src/sort.js +++ b/contentbank/amd/src/sort.js @@ -196,6 +196,8 @@ const updateSortButtons = (contentBank, sortButton) => { button.classList.remove('dir-desc'); button.classList.add('dir-none'); + button.closest(selectors.elements.cell).setAttribute('aria-sort', 'none'); + updateButtonTitle(button, false); } }); @@ -205,13 +207,16 @@ const updateSortButtons = (contentBank, sortButton) => { if (sortButton.classList.contains('dir-none')) { sortButton.classList.remove('dir-none'); sortButton.classList.add('dir-asc'); + sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending'); } else if (sortButton.classList.contains('dir-asc')) { sortButton.classList.remove('dir-asc'); sortButton.classList.add('dir-desc'); + sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'descending'); ascending = false; } else if (sortButton.classList.contains('dir-desc')) { sortButton.classList.remove('dir-desc'); sortButton.classList.add('dir-asc'); + sortButton.closest(selectors.elements.cell).setAttribute('aria-sort', 'ascending'); } updateButtonTitle(sortButton, ascending); diff --git a/contentbank/templates/bankcontent.mustache b/contentbank/templates/bankcontent.mustache index a96c57c1e10..5002708d414 100644 --- a/contentbank/templates/bankcontent.mustache +++ b/contentbank/templates/bankcontent.mustache @@ -125,60 +125,61 @@ data-region="contentbank">
{{#contents.0}} -
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -193,10 +194,9 @@ data-region="contentbank"> data-type="{{ type }}" data-author="{{ author }}">