diff --git a/course/amd/build/local/activitychooser/dialogue.min.js b/course/amd/build/local/activitychooser/dialogue.min.js index 03dfa69f834..042132f99e2 100644 --- a/course/amd/build/local/activitychooser/dialogue.min.js +++ b/course/amd/build/local/activitychooser/dialogue.min.js @@ -1,2 +1,2 @@ -define ("core_course/local/activitychooser/dialogue",["exports","jquery","core/modal_events","core_course/local/activitychooser/selectors","core/templates","core/key_codes","core/loadingicon","core_course/local/activitychooser/repository","core/notification"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.displayChooser=void 0;b=k(b);c=j(c);d=k(d);e=j(e);h=j(h);i=k(i);function j(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function k(a){return a&&a.__esModule?a:{default:a}}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}function n(a,b){return q(a)||p(a,b)||o()}function o(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}function p(a,b){var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function q(a){if(Array.isArray(a))return a}var r=function(a,b){var c=a.find(d.default.regions.help)[0];c.innerHTML="";var f=(0,g.addIconToContainer)(c),h=null,j=new Promise(function(a){h=a}),k=e.renderForPromise("core_course/chooser_help",b);Promise.all([k,f,j]).then(function(a){var b=n(a,1),d=b[0],f=d.html,g=d.js;return e.replaceNodeContents(c,f,g)}).then(function(){c.querySelector(d.default.regions.chooserSummary.description).focus();return c}).catch(i.default.exception);a.one("slid.bs.carousel",function(){h()});a.carousel("next")},s=function(){var a=m(regeneratorRuntime.mark(function a(b,c,d){var e,f,g,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:e=c.dataset.favourited;f=c.dataset.id;g=c.dataset.name;i=c.dataset.internal;if(!("true"===e)){a.next=10;break}a.next=7;return h.unfavouriteModule(g,f);case 7:d(i,!1,b);a.next=13;break;case 10:a.next=12;return h.favouriteModule(g,f);case 12:d(i,!0,b);case 13:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),t=function(a,c,f){var g=function(g){if(g.target.closest(d.default.actions.optionActions.showSummary)){var e=(0,b.default)(a.getBody()[0].querySelector(d.default.regions.carousel)),h=g.target.closest(d.default.regions.chooserOption.container),i=h.dataset.modname,j=c.get(i);r(e,j)}if(g.target.closest(d.default.actions.optionActions.manageFavourite)){var k=g.target.closest(d.default.actions.optionActions.manageFavourite);s(a.getBody()[0],k,f)}if(g.target.matches(d.default.actions.closeOption)){var l=(0,b.default)(a.getBody()[0].querySelector(d.default.regions.carousel));l.carousel("prev");l.on("slid.bs.carousel",function(){var b=a.getBody()[0].querySelector(d.default.regions.modules),c=b.querySelector(d.default.regions.getModuleSelector(g.target.dataset.modname));c.focus()})}};a.getBodyPromise().then(function(a){return a[0]}).then(function(a){(0,b.default)(a.querySelector(d.default.regions.carousel)).carousel({interval:!1,pause:!0,keyboard:!1});return a}).then(function(a){a.addEventListener("click",g);return a}).then(function(a){u(a,c);return a}).catch()},u=function(a,c){var e=a.querySelector(d.default.regions.favouriteTabNav),g=a.querySelector(d.default.regions.recommendedTabNav),h=a.querySelector(d.default.regions.defaultTabNav);[e,g,h].forEach(function(a){return a.addEventListener("keyup",function(a){var b=a.target.parentElement.parentElement.firstElementChild.firstElementChild,c=a.target.parentElement.parentElement.lastElementChild.firstElementChild;if(a.keyCode===f.arrowRight){var d=a.target.parentElement.nextElementSibling;if(null===d){a.srcElement.tabIndex=-1;b.tabIndex=0;b.focus()}else if(d.firstElementChild.classList.contains("d-none")){a.srcElement.tabIndex=-1;c.tabIndex=0;c.focus()}else{a.srcElement.tabIndex=-1;d.firstElementChild.tabIndex=0;d.firstElementChild.focus()}}if(a.keyCode===f.arrowLeft){var e=a.target.parentElement.previousElementSibling;if(null===e){a.srcElement.tabIndex=-1;c.tabIndex=0;c.focus()}else if(e.firstElementChild.classList.contains("d-none")){a.srcElement.tabIndex=-1;b.tabIndex=0;b.focus()}else{a.srcElement.tabIndex=-1;e.firstElementChild.tabIndex=0;e.firstElementChild.focus()}}if(a.keyCode===f.home){a.srcElement.tabIndex=-1;b.tabIndex=0;b.focus()}if(a.keyCode===f.end){a.srcElement.tabIndex=-1;c.tabIndex=0;c.focus()}if(a.keyCode===f.space){a.preventDefault();a.target.click()}})});var i=a.querySelectorAll(d.default.regions.chooserOption.container);Array.from(i).forEach(function(e){return e.addEventListener("keyup",function(g){var e=document.querySelector(d.default.regions.chooserOptions);if(g.keyCode===f.enter||g.keyCode===f.space){if(g.target.matches(d.default.actions.optionActions.showSummary)){g.preventDefault();var h=g.target.closest(d.default.regions.chooserOption.container),i=h.dataset.modname,j=c.get(i),k=(0,b.default)(a.querySelector(d.default.regions.carousel));k.carousel({interval:!1,pause:!0,keyboard:!1});r(k,j)}}if(g.keyCode===f.arrowRight){g.preventDefault();var l=g.target.closest(d.default.regions.chooserOption.container),m=l.nextElementSibling,n=e.firstElementChild,o=w(m,n);v(o,l)}if(g.keyCode===f.arrowLeft){g.preventDefault();var p=g.target.closest(d.default.regions.chooserOption.container),q=p.previousElementSibling,s=e.lastElementChild,t=w(q,s);v(t,p)}if(g.keyCode===f.home){g.preventDefault();var u=g.target.closest(d.default.regions.chooserOption.container),x=e.firstElementChild;v(x,u)}if(g.keyCode===f.end){g.preventDefault();var y=g.target.closest(d.default.regions.chooserOption.container),z=e.lastElementChild;v(z,y)}})})},v=function(a){var b=1.\n\n/**\n * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\n\n/**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n */\nconst showModuleHelp = (carousel, moduleData) => {\n const help = carousel.find(selectors.regions.help)[0];\n help.innerHTML = '';\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise('core_course/chooser_help', moduleData);\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.description).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite) => {\n const bodyClickListener = e => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n showModuleHelp(carousel, moduleData);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n initKeyboardNavigation(body, mappedModules);\n return body;\n })\n .catch();\n\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser.\n *\n * @method initKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n */\nconst initKeyboardNavigation = (body, mappedModules) => {\n\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keyup', (e) => {\n const firstLink = e.target.parentElement.parentElement.firstElementChild.firstElementChild;\n const lastLink = e.target.parentElement.parentElement.lastElementChild.firstElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.parentElement.nextElementSibling;\n if (nextLink === null) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.firstElementChild.classList.contains('d-none')) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.srcElement.tabIndex = -1;\n nextLink.firstElementChild.tabIndex = 0;\n nextLink.firstElementChild.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.parentElement.previousElementSibling;\n if (previousLink === null) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.firstElementChild.classList.contains('d-none')) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.srcElement.tabIndex = -1;\n previousLink.firstElementChild.tabIndex = 0;\n previousLink.firstElementChild.focus();\n }\n }\n if (e.keyCode === home) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n\n // Set up the handlers for the modules.\n const chooserOptions = body.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keyup', (e) => {\n const chooserOptions = document.querySelector(selectors.regions.chooserOptions);\n\n // Check for enter/ space triggers for showing the help.\n if (e.keyCode === enter || e.keyCode === space) {\n if (e.target.matches(selectors.actions.optionActions.showSummary)) {\n e.preventDefault();\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n showModuleHelp(carousel, moduleData);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptions.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptions.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptions.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptions.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = false) => {\n if (previousChooserOption !== false) {\n const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser);\n const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const previousChooserOptionFavourite = previousChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n previousChooserOption.tabIndex = -1;\n previousChooserOptionLink.tabIndex = -1;\n previousChooserOptionHelp.tabIndex = -1;\n previousChooserOptionFavourite.tabIndex = -1;\n }\n\n const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser);\n const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const currentChooserOptionFavourite = currentChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n // Set tabindex to 0 to add current chooser option element to the focus order.\n currentChooserOption.tabIndex = 0;\n currentChooserOptionLink.tabIndex = 0;\n currentChooserOptionHelp.tabIndex = 0;\n currentChooserOptionFavourite.tabIndex = 0;\n // Focus the current chooser option element.\n currentChooserOption.focus();\n};\n\n/**\n * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {String}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Display the module chooser.\n *\n * @method displayChooser\n * @param {HTMLElement} origin The calling button\n * @param {Object} modal Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nexport const displayChooser = (origin, modal, sectionModules, partialFavourite) => {\n\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n registerListenerEvents(modal, mappedModules, partialFavourite);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n modal.getRoot().on(ModalEvents.shown, () => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise()\n .then(body => {\n const firstChooserOption = body[0].querySelector(selectors.regions.chooserOption.container);\n focusChooserOption(firstChooserOption);\n\n return;\n })\n .catch(Notification.exception);\n });\n\n modal.show();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/dialogue.js"],"names":["showModuleHelp","carousel","moduleData","help","find","selectors","regions","innerHTML","spinnerPromise","transitionPromiseResolver","transitionPromise","Promise","resolve","contentPromise","Templates","renderForPromise","all","then","html","js","replaceNodeContents","querySelector","chooserSummary","description","focus","catch","Notification","exception","one","manageFavouriteState","modalBody","caller","partialFavourite","isFavourite","dataset","favourited","id","name","internal","Repository","unfavouriteModule","favouriteModule","registerListenerEvents","modal","mappedModules","bodyClickListener","e","target","closest","actions","optionActions","showSummary","getBody","module","chooserOption","container","moduleName","modname","get","manageFavourite","matches","closeOption","on","allModules","modules","getModuleSelector","clearSearch","searchInput","search","value","toggleSearchResultsView","getBodyPromise","body","interval","pause","keyboard","addEventListener","activeSectionId","elements","activetab","getAttribute","sectionChooserOptions","getSectionChooserOptions","firstChooserOption","toggleFocusableChooserOption","initTabsKeyboardNavigation","initChooserOptionsKeyboardNavigation","favTabNav","favouriteTabNav","recommendedTabNav","defaultTabNav","forEach","element","firstLink","parentElement","firstElementChild","lastLink","lastElementChild","keyCode","arrowRight","nextLink","nextElementSibling","srcElement","tabIndex","classList","contains","arrowLeft","previousLink","previousElementSibling","home","end","space","preventDefault","click","chooserOptionsContainer","chooserOptions","querySelectorAll","Array","from","enter","currentOption","nextOption","firstOption","toFocusOption","clickErrorHandler","focusChooserOption","previousOption","lastOption","currentChooserOption","previousChooserOption","isFocusable","chooserOptionLink","addChooser","chooserOptionHelp","chooserOptionFavourite","item","fallback","renderSearchResults","searchResultsContainer","searchResultsData","templateData","length","searchQuery","searchResults","chooserContainer","chooser","clearSearchButton","clearsearch","searchIcon","searchicon","searchModules","searchResultItemsContainer","searchResultItems","firstSearchResultItem","add","remove","setAttribute","removeAttribute","searchTerm","toLowerCase","activity","activityName","title","activityDesc","includes","push","disableFocusAllChooserOptions","allChooserOptions","displayChooser","origin","sectionModules","Map","set","componentname","link","getRoot","ModalEvents","hidden","destroy","shown","getModal","tab","activeSectionChooserOptions","prevActiveSectionId","relatedTarget","prevActiveSectionChooserOptions","show"],"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 * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\nimport {debounce} from 'core/utils';\n\n/**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n */\nconst showModuleHelp = (carousel, moduleData) => {\n const help = carousel.find(selectors.regions.help)[0];\n help.innerHTML = '';\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise('core_course/chooser_help', moduleData);\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.description).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite) => {\n const bodyClickListener = e => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n showModuleHelp(carousel, moduleData);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n\n // The \"clear search\" button is triggered.\n if (e.target.closest(selectors.actions.clearSearch)) {\n // Clear the entered search query in the search bar and hide the search results container.\n const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value);\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Add a listener for an input change in the activity chooser's search bar.\n .then(body => {\n const searchInput = body.querySelector(selectors.actions.search);\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(body, mappedModules, searchInput.value);\n }, 300));\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n // Get the active chooser options section.\n const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);\n toggleFocusableChooserOption(firstChooserOption, true);\n initTabsKeyboardNavigation(body);\n initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions);\n return body;\n })\n .catch();\n\n};\n\n/**\n * Initialise the keyboard navigation controls for the tab list items.\n *\n * @method initTabsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n */\nconst initTabsKeyboardNavigation = (body) => {\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keyup', (e) => {\n const firstLink = e.target.parentElement.parentElement.firstElementChild.firstElementChild;\n const lastLink = e.target.parentElement.parentElement.lastElementChild.firstElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.parentElement.nextElementSibling;\n if (nextLink === null) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.firstElementChild.classList.contains('d-none')) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.srcElement.tabIndex = -1;\n nextLink.firstElementChild.tabIndex = 0;\n nextLink.firstElementChild.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.parentElement.previousElementSibling;\n if (previousLink === null) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.firstElementChild.classList.contains('d-none')) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.srcElement.tabIndex = -1;\n previousLink.firstElementChild.tabIndex = 0;\n previousLink.firstElementChild.focus();\n }\n }\n if (e.keyCode === home) {\n e.srcElement.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.srcElement.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser options.\n *\n * @method initChooserOptionsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items\n */\nconst initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => {\n const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keyup', (e) => {\n\n // Check for enter/ space triggers for showing the help.\n if (e.keyCode === enter || e.keyCode === space) {\n if (e.target.matches(selectors.actions.optionActions.showSummary)) {\n e.preventDefault();\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n showModuleHelp(carousel, moduleData);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptionsContainer.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptionsContainer.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptionsContainer.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptionsContainer.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement|null} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = null) => {\n if (previousChooserOption !== null) {\n toggleFocusableChooserOption(previousChooserOption, false);\n }\n\n toggleFocusableChooserOption(currentChooserOption, true);\n currentChooserOption.focus();\n};\n\n/**\n * Add or remove a chooser option from the focus order.\n *\n * @method toggleFocusableChooserOption\n * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order\n * @param {Boolean} isFocusable Whether the chooser element is focusable or not\n */\nconst toggleFocusableChooserOption = (chooserOption, isFocusable) => {\n const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);\n const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n\n if (isFocusable) {\n // Set tabindex to 0 to add current chooser option element to the focus order.\n chooserOption.tabIndex = 0;\n chooserOptionLink.tabIndex = 0;\n chooserOptionHelp.tabIndex = 0;\n chooserOptionFavourite.tabIndex = 0;\n } else {\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n chooserOption.tabIndex = -1;\n chooserOptionLink.tabIndex = -1;\n chooserOptionHelp.tabIndex = -1;\n chooserOptionFavourite.tabIndex = -1;\n }\n};\n\n/**\n * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Render the search results in a defined container\n *\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The container where the data should be rendered\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresultsnumber': searchResultsData.length,\n 'searchresults': searchResultsData\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_course/chooser_search_results', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {HTMLElement} modalBody The body of the created modal for the section\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {String} searchQuery The search query\n */\nconst toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => {\n const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);\n const chooserContainer = modalBody.querySelector(selectors.regions.chooser);\n const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);\n const searchIcon = modalBody.querySelector(selectors.elements.searchicon);\n\n if (searchQuery.length > 0) { // Search query is present.\n const searchResultsData = searchModules(mappedModules, searchQuery);\n await renderSearchResults(searchResultsContainer, searchResultsData);\n const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);\n const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer);\n }\n // Display the \"clear\" search button in the activity chooser search bar.\n searchIcon.classList.add('d-none');\n clearSearchButton.classList.remove('d-none');\n // Hide the default chooser options container.\n chooserContainer.setAttribute('hidden', 'hidden');\n // Display the search results container.\n searchResultsContainer.removeAttribute('hidden');\n } else { // Search query is not present.\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n searchIcon.classList.remove('d-none');\n // Hide the search results container.\n searchResultsContainer.setAttribute('hidden', 'hidden');\n // Display the default chooser options container.\n chooserContainer.removeAttribute('hidden');\n }\n};\n\n/**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {Array} modules List of available modules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchModules = (modules, searchTerm) => {\n if (searchTerm === '') {\n return modules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n modules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n};\n\n/**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items\n */\nconst disableFocusAllChooserOptions = (sectionChooserOptions) => {\n const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n toggleFocusableChooserOption(chooserOption, false);\n });\n};\n\n/**\n * Display the module chooser.\n *\n * @method displayChooser\n * @param {HTMLElement} origin The calling button\n * @param {Object} modal Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nexport const displayChooser = (origin, modal, sectionModules, partialFavourite) => {\n\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n registerListenerEvents(modal, mappedModules, partialFavourite);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n modal.getRoot().on(ModalEvents.shown, () => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise()\n .then(body => {\n $(selectors.elements.tab).on('shown.bs.tab', (e) => {\n const activeSectionId = e.target.getAttribute(\"href\");\n const activeSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = activeSectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n const prevActiveSectionId = e.relatedTarget.getAttribute(\"href\");\n const prevActiveSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));\n\n // Disable the focus of every chooser option in the previous active section.\n disableFocusAllChooserOptions(prevActiveSectionChooserOptions);\n // Enable the focus of the first chooser option in the current active section.\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);\n });\n return;\n })\n .catch(Notification.exception);\n });\n\n modal.show();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file diff --git a/course/amd/build/local/activitychooser/selectors.min.js b/course/amd/build/local/activitychooser/selectors.min.js index 73c917e791d..0aee2dcf3e4 100644 --- a/course/amd/build/local/activitychooser/selectors.min.js +++ b/course/amd/build/local/activitychooser/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_course/local/activitychooser/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:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default}); +define ("core_course/local/activitychooser/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:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",searchicon:".searchbar-append .search-icon",clearsearch:".searchbar-append .clear"}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/course/amd/build/local/activitychooser/selectors.min.js.map b/course/amd/build/local/activitychooser/selectors.min.js.map index b3e6a0c631b..da656df5c5a 100644 --- a/course/amd/build/local/activitychooser/selectors.min.js.map +++ b/course/amd/build/local/activitychooser/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/activitychooser/selectors.js"],"names":["getDataSelector","name","value","regions","chooser","chooserOptions","chooserOption","container","actions","info","chooserSummary","content","description","carousel","help","modules","favouriteTabNav","recommendedTabNav","defaultTabNav","favouriteTab","recommendedTab","defaultTab","getModuleSelector","modname","optionActions","showSummary","manageFavourite","addChooser","closeOption","hide","render","favourites","elements","section","sectionmodchooser","sitemenu","sitetopic"],"mappings":"gKA+BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,OAAO,CAAEJ,CAAe,CAAC,QAAD,CAAW,mBAAX,CADnB,CAELK,cAAc,CAAEL,CAAe,CAAC,QAAD,CAAW,2BAAX,CAF1B,CAGLM,aAAa,CAAE,CACXC,SAAS,CAAEP,CAAe,CAAC,QAAD,CAAW,0BAAX,CADf,CAEXQ,OAAO,CAAER,CAAe,CAAC,QAAD,CAAW,kCAAX,CAFb,CAGXS,IAAI,CAAET,CAAe,CAAC,QAAD,CAAW,+BAAX,CAHV,CAHV,CAQLU,cAAc,CAAE,CACZH,SAAS,CAAEP,CAAe,CAAC,QAAD,CAAW,kCAAX,CADd,CAEZW,OAAO,CAAEX,CAAe,CAAC,QAAD,CAAW,0CAAX,CAFZ,CAGZY,WAAW,CAAEZ,CAAe,CAAC,QAAD,CAAW,qBAAX,CAHhB,CAIZQ,OAAO,CAAER,CAAe,CAAC,QAAD,CAAW,0CAAX,CAJZ,CARX,CAcLa,QAAQ,CAAEb,CAAe,CAAC,QAAD,CAAW,UAAX,CAdpB,CAeLc,IAAI,CAAEd,CAAe,CAAC,QAAD,CAAW,MAAX,CAfhB,CAgBLe,OAAO,CAAEf,CAAe,CAAC,QAAD,CAAW,SAAX,CAhBnB,CAiBLgB,eAAe,CAAEhB,CAAe,CAAC,QAAD,CAAW,mBAAX,CAjB3B,CAkBLiB,iBAAiB,CAAEjB,CAAe,CAAC,QAAD,CAAW,qBAAX,CAlB7B,CAmBLkB,aAAa,CAAElB,CAAe,CAAC,QAAD,CAAW,iBAAX,CAnBzB,CAoBLmB,YAAY,CAAEnB,CAAe,CAAC,QAAD,CAAW,YAAX,CApBxB,CAqBLoB,cAAc,CAAEpB,CAAe,CAAC,QAAD,CAAW,aAAX,CArB1B,CAsBLqB,UAAU,CAAErB,CAAe,CAAC,QAAD,CAAW,SAAX,CAtBtB,CAuBLsB,iBAAiB,CAAE,2BAAAC,CAAO,qDAAuCA,CAAvC,QAvBrB,CADE,CA0BXf,OAAO,CAAE,CACLgB,aAAa,CAAE,CACXC,WAAW,CAAEzB,CAAe,CAAC,QAAD,CAAW,qBAAX,CADjB,CAEX0B,eAAe,CAAE1B,CAAe,CAAC,QAAD,CAAW,yBAAX,CAFrB,CADV,CAKL2B,UAAU,CAAE3B,CAAe,CAAC,QAAD,CAAW,oBAAX,CALtB,CAML4B,WAAW,CAAE5B,CAAe,CAAC,QAAD,CAAW,8BAAX,CANvB,CAOL6B,IAAI,CAAE7B,CAAe,CAAC,QAAD,CAAW,MAAX,CAPhB,CA1BE,CAmCX8B,MAAM,CAAE,CACJC,UAAU,CAAE/B,CAAe,CAAC,QAAD,CAAW,iBAAX,CADvB,CAnCG,CAsCXgC,QAAQ,CAAE,CACNC,OAAO,CAAE,UADH,CAENC,iBAAiB,CAAE,gCAFb,CAGNC,QAAQ,CAAE,uBAHJ,CAINC,SAAS,CAAE,eAJL,CAtCC,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 grading interface.\n *\n * @module core_course/local/chooser/selectors\n * @package core_course\n * @copyright 2019 Mathew May \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 * @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 chooser: getDataSelector('region', 'chooser-container'),\n chooserOptions: getDataSelector('region', 'chooser-options-container'),\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n description: getDataSelector('region', 'summary-description'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n modules: getDataSelector('region', 'modules'),\n favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),\n recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),\n defaultTabNav: getDataSelector('region', 'default-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n recommendedTab: getDataSelector('region', 'recommended'),\n defaultTab: getDataSelector('region', 'default'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide')\n },\n render: {\n favourites: getDataSelector('render', 'favourites-area'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n sitemenu: '.block_site_main_menu',\n sitetopic: 'div.sitetopic',\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/selectors.js"],"names":["getDataSelector","name","value","regions","chooser","getSectionChooserOptions","containerid","chooserOption","container","actions","info","chooserSummary","content","description","carousel","help","modules","favouriteTabNav","recommendedTabNav","defaultTabNav","favouriteTab","recommendedTab","defaultTab","getModuleSelector","modname","searchResults","searchResultItems","optionActions","showSummary","manageFavourite","addChooser","closeOption","hide","search","clearSearch","render","favourites","elements","section","sectionmodchooser","sitemenu","sitetopic","tab","activetab","searchicon","clearsearch"],"mappings":"gKA+BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,OAAO,CAAEJ,CAAe,CAAC,QAAD,CAAW,mBAAX,CADnB,CAELK,wBAAwB,CAAE,kCAAAC,CAAW,kBAAOA,CAAP,aAAsBN,CAAe,CAAC,QAAD,CAAW,2BAAX,CAArC,EAFhC,CAGLO,aAAa,CAAE,CACXC,SAAS,CAAER,CAAe,CAAC,QAAD,CAAW,0BAAX,CADf,CAEXS,OAAO,CAAET,CAAe,CAAC,QAAD,CAAW,kCAAX,CAFb,CAGXU,IAAI,CAAEV,CAAe,CAAC,QAAD,CAAW,+BAAX,CAHV,CAHV,CAQLW,cAAc,CAAE,CACZH,SAAS,CAAER,CAAe,CAAC,QAAD,CAAW,kCAAX,CADd,CAEZY,OAAO,CAAEZ,CAAe,CAAC,QAAD,CAAW,0CAAX,CAFZ,CAGZa,WAAW,CAAEb,CAAe,CAAC,QAAD,CAAW,qBAAX,CAHhB,CAIZS,OAAO,CAAET,CAAe,CAAC,QAAD,CAAW,0CAAX,CAJZ,CARX,CAcLc,QAAQ,CAAEd,CAAe,CAAC,QAAD,CAAW,UAAX,CAdpB,CAeLe,IAAI,CAAEf,CAAe,CAAC,QAAD,CAAW,MAAX,CAfhB,CAgBLgB,OAAO,CAAEhB,CAAe,CAAC,QAAD,CAAW,SAAX,CAhBnB,CAiBLiB,eAAe,CAAEjB,CAAe,CAAC,QAAD,CAAW,mBAAX,CAjB3B,CAkBLkB,iBAAiB,CAAElB,CAAe,CAAC,QAAD,CAAW,qBAAX,CAlB7B,CAmBLmB,aAAa,CAAEnB,CAAe,CAAC,QAAD,CAAW,iBAAX,CAnBzB,CAoBLoB,YAAY,CAAEpB,CAAe,CAAC,QAAD,CAAW,YAAX,CApBxB,CAqBLqB,cAAc,CAAErB,CAAe,CAAC,QAAD,CAAW,aAAX,CArB1B,CAsBLsB,UAAU,CAAEtB,CAAe,CAAC,QAAD,CAAW,SAAX,CAtBtB,CAuBLuB,iBAAiB,CAAE,2BAAAC,CAAO,qDAAuCA,CAAvC,QAvBrB,CAwBLC,aAAa,CAAEzB,CAAe,CAAC,QAAD,CAAW,0BAAX,CAxBzB,CAyBL0B,iBAAiB,CAAE1B,CAAe,CAAC,QAAD,CAAW,+BAAX,CAzB7B,CADE,CA4BXS,OAAO,CAAE,CACLkB,aAAa,CAAE,CACXC,WAAW,CAAE5B,CAAe,CAAC,QAAD,CAAW,qBAAX,CADjB,CAEX6B,eAAe,CAAE7B,CAAe,CAAC,QAAD,CAAW,yBAAX,CAFrB,CADV,CAKL8B,UAAU,CAAE9B,CAAe,CAAC,QAAD,CAAW,oBAAX,CALtB,CAML+B,WAAW,CAAE/B,CAAe,CAAC,QAAD,CAAW,8BAAX,CANvB,CAOLgC,IAAI,CAAEhC,CAAe,CAAC,QAAD,CAAW,MAAX,CAPhB,CAQLiC,MAAM,CAAEjC,CAAe,CAAC,QAAD,CAAW,QAAX,CARlB,CASLkC,WAAW,CAAElC,CAAe,CAAC,QAAD,CAAW,aAAX,CATvB,CA5BE,CAuCXmC,MAAM,CAAE,CACJC,UAAU,CAAEpC,CAAe,CAAC,QAAD,CAAW,iBAAX,CADvB,CAvCG,CA0CXqC,QAAQ,CAAE,CACNC,OAAO,CAAE,UADH,CAENC,iBAAiB,CAAE,gCAFb,CAGNC,QAAQ,CAAE,uBAHJ,CAINC,SAAS,CAAE,eAJL,CAKNC,GAAG,CAAE,wBALC,CAMNC,SAAS,CAAE,gDANL,CAONC,UAAU,CAAE,gCAPN,CAQNC,WAAW,CAAE,0BARP,CA1CC,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 grading interface.\n *\n * @module core_course/local/chooser/selectors\n * @package core_course\n * @copyright 2019 Mathew May \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 * @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 chooser: getDataSelector('region', 'chooser-container'),\n getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n description: getDataSelector('region', 'summary-description'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n modules: getDataSelector('region', 'modules'),\n favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),\n recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),\n defaultTabNav: getDataSelector('region', 'default-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n recommendedTab: getDataSelector('region', 'recommended'),\n defaultTab: getDataSelector('region', 'default'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`,\n searchResults: getDataSelector('region', 'search-results-container'),\n searchResultItems: getDataSelector('region', 'search-result-items-container'),\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide'),\n search: getDataSelector('action', 'search'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n },\n render: {\n favourites: getDataSelector('render', 'favourites-area'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n sitemenu: '.block_site_main_menu',\n sitetopic: 'div.sitetopic',\n tab: 'a[data-toggle=\"tab\"]',\n activetab: 'a[data-toggle=\"tab\"][aria-selected=\"true\"]',\n searchicon: '.searchbar-append .search-icon',\n clearsearch: '.searchbar-append .clear'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/course/amd/src/local/activitychooser/dialogue.js b/course/amd/src/local/activitychooser/dialogue.js index 9d7b73cf346..9429ee68c40 100644 --- a/course/amd/src/local/activitychooser/dialogue.js +++ b/course/amd/src/local/activitychooser/dialogue.js @@ -30,6 +30,7 @@ import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes'; import {addIconToContainer} from 'core/loadingicon'; import * as Repository from 'core_course/local/activitychooser/repository'; import Notification from 'core/notification'; +import {debounce} from 'core/utils'; /** * Given an event from the main module 'page' navigate to it's help section via a carousel. @@ -134,6 +135,15 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { caller.focus(); }); } + + // The "clear search" button is triggered. + if (e.target.closest(selectors.actions.clearSearch)) { + // Clear the entered search query in the search bar and hide the search results container. + const searchInput = modal.getBody()[0].querySelector(selectors.actions.search); + searchInput.value = ""; + searchInput.focus(); + toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value); + } }; modal.getBodyPromise() @@ -159,9 +169,26 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { return body; }) + // Add a listener for an input change in the activity chooser's search bar. + .then(body => { + const searchInput = body.querySelector(selectors.actions.search); + // The search input is triggered. + searchInput.addEventListener('input', debounce(() => { + // Display the search results. + toggleSearchResultsView(body, mappedModules, searchInput.value); + }, 300)); + return body; + }) + // Register event listeners related to the keyboard navigation controls. .then(body => { - initKeyboardNavigation(body, mappedModules); + // Get the active chooser options section. + const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute("href"); + const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId)); + const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container); + toggleFocusableChooserOption(firstChooserOption, true); + initTabsKeyboardNavigation(body); + initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions); return body; }) .catch(); @@ -169,14 +196,12 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { }; /** - * Initialise the keyboard navigation controls for the chooser. + * Initialise the keyboard navigation controls for the tab list items. * - * @method initKeyboardNavigation + * @method initTabsKeyboardNavigation * @param {HTMLElement} body Our modal that we are working with - * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} */ -const initKeyboardNavigation = (body, mappedModules) => { - +const initTabsKeyboardNavigation = (body) => { // Set up the tab handlers. const favTabNav = body.querySelector(selectors.regions.favouriteTabNav); const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav); @@ -235,13 +260,21 @@ const initKeyboardNavigation = (body, mappedModules) => { } }); }); +}; - // Set up the handlers for the modules. - const chooserOptions = body.querySelectorAll(selectors.regions.chooserOption.container); +/** + * Initialise the keyboard navigation controls for the chooser options. + * + * @method initChooserOptionsKeyboardNavigation + * @param {HTMLElement} body Our modal that we are working with + * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} + * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items + */ +const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => { + const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container); Array.from(chooserOptions).forEach((element) => { return element.addEventListener('keyup', (e) => { - const chooserOptions = document.querySelector(selectors.regions.chooserOptions); // Check for enter/ space triggers for showing the help. if (e.keyCode === enter || e.keyCode === space) { @@ -265,7 +298,7 @@ const initKeyboardNavigation = (body, mappedModules) => { e.preventDefault(); const currentOption = e.target.closest(selectors.regions.chooserOption.container); const nextOption = currentOption.nextElementSibling; - const firstOption = chooserOptions.firstElementChild; + const firstOption = chooserOptionsContainer.firstElementChild; const toFocusOption = clickErrorHandler(nextOption, firstOption); focusChooserOption(toFocusOption, currentOption); } @@ -275,7 +308,7 @@ const initKeyboardNavigation = (body, mappedModules) => { e.preventDefault(); const currentOption = e.target.closest(selectors.regions.chooserOption.container); const previousOption = currentOption.previousElementSibling; - const lastOption = chooserOptions.lastElementChild; + const lastOption = chooserOptionsContainer.lastElementChild; const toFocusOption = clickErrorHandler(previousOption, lastOption); focusChooserOption(toFocusOption, currentOption); } @@ -283,14 +316,14 @@ const initKeyboardNavigation = (body, mappedModules) => { if (e.keyCode === home) { e.preventDefault(); const currentOption = e.target.closest(selectors.regions.chooserOption.container); - const firstOption = chooserOptions.firstElementChild; + const firstOption = chooserOptionsContainer.firstElementChild; focusChooserOption(firstOption, currentOption); } if (e.keyCode === end) { e.preventDefault(); const currentOption = e.target.closest(selectors.regions.chooserOption.container); - const lastOption = chooserOptions.lastElementChild; + const lastOption = chooserOptionsContainer.lastElementChild; focusChooserOption(lastOption, currentOption); } }); @@ -302,39 +335,51 @@ const initKeyboardNavigation = (body, mappedModules) => { * * @method focusChooserOption * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus - * @param {HTMLElement} previousChooserOption The previous focused option element + * @param {HTMLElement|null} previousChooserOption The previous focused option element */ -const focusChooserOption = (currentChooserOption, previousChooserOption = false) => { - if (previousChooserOption !== false) { - const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser); - const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary); - const previousChooserOptionFavourite = previousChooserOption.querySelector(selectors.actions.optionActions.manageFavourite); - // Set tabindex to -1 to remove the previous chooser option element from the focus order. - previousChooserOption.tabIndex = -1; - previousChooserOptionLink.tabIndex = -1; - previousChooserOptionHelp.tabIndex = -1; - previousChooserOptionFavourite.tabIndex = -1; +const focusChooserOption = (currentChooserOption, previousChooserOption = null) => { + if (previousChooserOption !== null) { + toggleFocusableChooserOption(previousChooserOption, false); } - const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser); - const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary); - const currentChooserOptionFavourite = currentChooserOption.querySelector(selectors.actions.optionActions.manageFavourite); - // Set tabindex to 0 to add current chooser option element to the focus order. - currentChooserOption.tabIndex = 0; - currentChooserOptionLink.tabIndex = 0; - currentChooserOptionHelp.tabIndex = 0; - currentChooserOptionFavourite.tabIndex = 0; - // Focus the current chooser option element. + toggleFocusableChooserOption(currentChooserOption, true); currentChooserOption.focus(); }; +/** + * Add or remove a chooser option from the focus order. + * + * @method toggleFocusableChooserOption + * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order + * @param {Boolean} isFocusable Whether the chooser element is focusable or not + */ +const toggleFocusableChooserOption = (chooserOption, isFocusable) => { + const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser); + const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary); + const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite); + + if (isFocusable) { + // Set tabindex to 0 to add current chooser option element to the focus order. + chooserOption.tabIndex = 0; + chooserOptionLink.tabIndex = 0; + chooserOptionHelp.tabIndex = 0; + chooserOptionFavourite.tabIndex = 0; + } else { + // Set tabindex to -1 to remove the previous chooser option element from the focus order. + chooserOption.tabIndex = -1; + chooserOptionLink.tabIndex = -1; + chooserOptionHelp.tabIndex = -1; + chooserOptionFavourite.tabIndex = -1; + } +}; + /** * Small error handling function to make sure the navigated to object exists * * @method clickErrorHandler * @param {HTMLElement} item What we want to check exists * @param {HTMLElement} fallback If we dont match anything fallback the focus - * @return {String} + * @return {HTMLElement} */ const clickErrorHandler = (item, fallback) => { if (item !== null) { @@ -344,6 +389,104 @@ const clickErrorHandler = (item, fallback) => { } }; +/** + * Render the search results in a defined container + * + * @method renderSearchResults + * @param {HTMLElement} searchResultsContainer The container where the data should be rendered + * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria + */ +const renderSearchResults = async(searchResultsContainer, searchResultsData) => { + const templateData = { + 'searchresultsnumber': searchResultsData.length, + 'searchresults': searchResultsData + }; + // Build up the html & js ready to place into the help section. + const {html, js} = await Templates.renderForPromise('core_course/chooser_search_results', templateData); + await Templates.replaceNodeContents(searchResultsContainer, html, js); +}; + +/** + * Toggle (display/hide) the search results depending on the value of the search query + * + * @method toggleSearchResultsView + * @param {HTMLElement} modalBody The body of the created modal for the section + * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} + * @param {String} searchQuery The search query + */ +const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => { + const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults); + const chooserContainer = modalBody.querySelector(selectors.regions.chooser); + const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch); + const searchIcon = modalBody.querySelector(selectors.elements.searchicon); + + if (searchQuery.length > 0) { // Search query is present. + const searchResultsData = searchModules(mappedModules, searchQuery); + await renderSearchResults(searchResultsContainer, searchResultsData); + const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems); + const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container); + if (firstSearchResultItem) { + // Set the first result item to be focusable. + toggleFocusableChooserOption(firstSearchResultItem, true); + // Register keyboard events on the created search result items. + initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer); + } + // Display the "clear" search button in the activity chooser search bar. + searchIcon.classList.add('d-none'); + clearSearchButton.classList.remove('d-none'); + // Hide the default chooser options container. + chooserContainer.setAttribute('hidden', 'hidden'); + // Display the search results container. + searchResultsContainer.removeAttribute('hidden'); + } else { // Search query is not present. + // Hide the "clear" search button in the activity chooser search bar. + clearSearchButton.classList.add('d-none'); + searchIcon.classList.remove('d-none'); + // Hide the search results container. + searchResultsContainer.setAttribute('hidden', 'hidden'); + // Display the default chooser options container. + chooserContainer.removeAttribute('hidden'); + } +}; + +/** + * Return the list of modules which have a name or description that matches the given search term. + * + * @method searchModules + * @param {Array} modules List of available modules + * @param {String} searchTerm The search term to match + * @return {Array} + */ +const searchModules = (modules, searchTerm) => { + if (searchTerm === '') { + return modules; + } + searchTerm = searchTerm.toLowerCase(); + const searchResults = []; + modules.forEach((activity) => { + const activityName = activity.title.toLowerCase(); + const activityDesc = activity.help.toLowerCase(); + if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) { + searchResults.push(activity); + } + }); + + return searchResults; +}; + +/** + * Disable the focus of all chooser options in a specific container (section). + * + * @method disableFocusAllChooserOptions + * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items + */ +const disableFocusAllChooserOptions = (sectionChooserOptions) => { + const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container); + allChooserOptions.forEach((chooserOption) => { + toggleFocusableChooserOption(chooserOption, false); + }); +}; + /** * Display the module chooser. * @@ -375,9 +518,22 @@ export const displayChooser = (origin, modal, sectionModules, partialFavourite) modal.getBodyPromise() .then(body => { - const firstChooserOption = body[0].querySelector(selectors.regions.chooserOption.container); - focusChooserOption(firstChooserOption); + $(selectors.elements.tab).on('shown.bs.tab', (e) => { + const activeSectionId = e.target.getAttribute("href"); + const activeSectionChooserOptions = body[0] + .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId)); + const firstChooserOption = activeSectionChooserOptions + .querySelector(selectors.regions.chooserOption.container); + const prevActiveSectionId = e.relatedTarget.getAttribute("href"); + const prevActiveSectionChooserOptions = body[0] + .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId)); + // Disable the focus of every chooser option in the previous active section. + disableFocusAllChooserOptions(prevActiveSectionChooserOptions); + // Enable the focus of the first chooser option in the current active section. + toggleFocusableChooserOption(firstChooserOption, true); + initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions); + }); return; }) .catch(Notification.exception); diff --git a/course/amd/src/local/activitychooser/selectors.js b/course/amd/src/local/activitychooser/selectors.js index f2f10ea5a1d..dc653bf6251 100644 --- a/course/amd/src/local/activitychooser/selectors.js +++ b/course/amd/src/local/activitychooser/selectors.js @@ -36,7 +36,7 @@ const getDataSelector = (name, value) => { export default { regions: { chooser: getDataSelector('region', 'chooser-container'), - chooserOptions: getDataSelector('region', 'chooser-options-container'), + getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`, chooserOption: { container: getDataSelector('region', 'chooser-option-container'), actions: getDataSelector('region', 'chooser-option-actions-container'), @@ -57,7 +57,9 @@ export default { favouriteTab: getDataSelector('region', 'favourites'), recommendedTab: getDataSelector('region', 'recommended'), defaultTab: getDataSelector('region', 'default'), - getModuleSelector: modname => `[role="menuitem"][data-modname="${modname}"]` + getModuleSelector: modname => `[role="menuitem"][data-modname="${modname}"]`, + searchResults: getDataSelector('region', 'search-results-container'), + searchResultItems: getDataSelector('region', 'search-result-items-container'), }, actions: { optionActions: { @@ -66,7 +68,9 @@ export default { }, addChooser: getDataSelector('action', 'add-chooser-option'), closeOption: getDataSelector('action', 'close-chooser-option-summary'), - hide: getDataSelector('action', 'hide') + hide: getDataSelector('action', 'hide'), + search: getDataSelector('action', 'search'), + clearSearch: getDataSelector('action', 'clearsearch'), }, render: { favourites: getDataSelector('render', 'favourites-area'), @@ -76,5 +80,9 @@ export default { sectionmodchooser: 'button.section-modchooser-link', sitemenu: '.block_site_main_menu', sitetopic: 'div.sitetopic', + tab: 'a[data-toggle="tab"]', + activetab: 'a[data-toggle="tab"][aria-selected="true"]', + searchicon: '.searchbar-append .search-icon', + clearsearch: '.searchbar-append .clear' }, }; diff --git a/course/templates/chooser.mustache b/course/templates/chooser.mustache index 14667b9555b..3c00b543e24 100644 --- a/course/templates/chooser.mustache +++ b/course/templates/chooser.mustache @@ -32,78 +32,80 @@ } } }} +