MDL-68529 course: Refactor chooser to include loading
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -78,7 +78,6 @@ const registerListenerEvents = (courseId) => {
|
||||
events.forEach((event) => {
|
||||
document.addEventListener(event, async(e) => {
|
||||
if (e.target.closest(selectors.elements.sectionmodchooser)) {
|
||||
const data = await fetchModuleData();
|
||||
// We need to know who called this.
|
||||
// Standard courses use the ID in the main section info.
|
||||
const sectionDiv = e.target.closest(selectors.elements.section);
|
||||
@@ -86,11 +85,31 @@ const registerListenerEvents = (courseId) => {
|
||||
const button = e.target.closest(selectors.elements.sectionmodchooser);
|
||||
// If we don't have a section ID use the fallback ID.
|
||||
const caller = sectionDiv || button;
|
||||
const favouriteFunction = partiallyAppliedFavouriteManager(data, caller.dataset.sectionid);
|
||||
const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
|
||||
const sectionModal = await modalBuilder(builtModuleData);
|
||||
|
||||
ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData, favouriteFunction);
|
||||
// We want to show the modal instantly but loading whilst waiting for our data.
|
||||
let bodyPromiseResolver;
|
||||
const bodyPromise = new Promise(resolve => {
|
||||
bodyPromiseResolver = resolve;
|
||||
});
|
||||
|
||||
const sectionModal = buildModal(bodyPromise);
|
||||
|
||||
// Now we have a modal we should start fetching data.
|
||||
const data = await fetchModuleData();
|
||||
|
||||
// Apply the section id to all the module instance links.
|
||||
const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
|
||||
|
||||
ChooserDialogue.displayChooser(
|
||||
sectionModal,
|
||||
builtModuleData,
|
||||
partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),
|
||||
);
|
||||
|
||||
bodyPromiseResolver(await Templates.render(
|
||||
'core_course/activitychooser',
|
||||
templateDataBuilder(builtModuleData)
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -102,7 +121,7 @@ const registerListenerEvents = (courseId) => {
|
||||
*
|
||||
* @method sectionIdMapper
|
||||
* @param {Object} webServiceData Our original data from the Web service call
|
||||
* @param {Array} id The ID of the section we need to append to the links
|
||||
* @param {Number} id The ID of the section we need to append to the links
|
||||
* @return {Array} [modules] with URL's built
|
||||
*/
|
||||
const sectionIdMapper = (webServiceData, id) => {
|
||||
@@ -114,15 +133,6 @@ const sectionIdMapper = (webServiceData, id) => {
|
||||
return newData.content_items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a modal on demand to save page load times
|
||||
*
|
||||
* @method modalBuilder
|
||||
* @param {Array} data our array of modules with section ID's applied in the URL field
|
||||
* @return {Object} Our modal that we are going to show the user
|
||||
*/
|
||||
const modalBuilder = data => buildModal(templateDataBuilder(data));
|
||||
|
||||
/**
|
||||
* Given an array of modules we want to figure out where & how to place them into our template object
|
||||
*
|
||||
@@ -158,18 +168,22 @@ const templateDataBuilder = (data) => {
|
||||
* Given an object we want to build a modal ready to show
|
||||
*
|
||||
* @method buildModal
|
||||
* @param {Object} data The template data which contains arrays of modules
|
||||
* @return {Object} The modal for the calling section with everything already set up
|
||||
* @param {Promise} bodyPromise
|
||||
* @return {Object} The modal ready to display immediately and render body in later.
|
||||
*/
|
||||
const buildModal = data => {
|
||||
const buildModal = bodyPromise => {
|
||||
return ModalFactory.create({
|
||||
type: ModalFactory.types.DEFAULT,
|
||||
title: getString('addresourceoractivity'),
|
||||
body: Templates.render('core_course/activitychooser', data),
|
||||
body: bodyPromise,
|
||||
large: true,
|
||||
templateContext: {
|
||||
classes: 'modchooser'
|
||||
}
|
||||
})
|
||||
.then(modal => {
|
||||
modal.show();
|
||||
return modal;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -240,6 +254,7 @@ const partiallyAppliedFavouriteManager = (moduleData, sectionId) => {
|
||||
if (favourite) {
|
||||
result.favourite = true;
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);
|
||||
|
||||
const builtFaves = sectionIdMapper(newFaves, sectionId);
|
||||
|
||||
@@ -42,6 +42,7 @@ import {debounce} from 'core/utils';
|
||||
const showModuleHelp = (carousel, moduleData) => {
|
||||
const help = carousel.find(selectors.regions.help)[0];
|
||||
help.innerHTML = '';
|
||||
help.classList.add('m-auto');
|
||||
|
||||
// Add a spinner.
|
||||
const spinnerPromise = addIconToContainer(help);
|
||||
@@ -483,6 +484,37 @@ const searchModules = (modules, searchTerm) => {
|
||||
return searchResults;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up our tabindex information across the chooser.
|
||||
*
|
||||
* @method setupKeyboardAccessibility
|
||||
* @param {Promise} modal Our created modal for the section
|
||||
* @param {Map} mappedModules A map of all of the built module information
|
||||
*/
|
||||
const setupKeyboardAccessibility = (modal, mappedModules) => {
|
||||
modal.getModal()[0].tabIndex = -1;
|
||||
|
||||
modal.getBodyPromise().then(body => {
|
||||
$(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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable the focus of all chooser options in a specific container (section).
|
||||
*
|
||||
@@ -500,13 +532,11 @@ const disableFocusAllChooserOptions = (sectionChooserOptions) => {
|
||||
* Display the module chooser.
|
||||
*
|
||||
* @method displayChooser
|
||||
* @param {HTMLElement} origin The calling button
|
||||
* @param {Object} modal Our created modal for the section
|
||||
* @param {Promise} modalPromise Our created modal for the section
|
||||
* @param {Array} sectionModules An array of all of the built module information
|
||||
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
|
||||
*/
|
||||
export const displayChooser = (origin, modal, sectionModules, partialFavourite) => {
|
||||
|
||||
export const displayChooser = (modalPromise, sectionModules, partialFavourite) => {
|
||||
// Make a map so we can quickly fetch a specific module's object for either rendering or searching.
|
||||
const mappedModules = new Map();
|
||||
sectionModules.forEach((module) => {
|
||||
@@ -514,39 +544,18 @@ export const displayChooser = (origin, modal, sectionModules, partialFavourite)
|
||||
});
|
||||
|
||||
// Register event listeners.
|
||||
registerListenerEvents(modal, mappedModules, partialFavourite);
|
||||
modalPromise.then(modal => {
|
||||
registerListenerEvents(modal, mappedModules, partialFavourite);
|
||||
|
||||
// We want to focus on the action select when the dialog is closed.
|
||||
modal.getRoot().on(ModalEvents.hidden, () => {
|
||||
modal.destroy();
|
||||
});
|
||||
// We want to focus on the first chooser option element as soon as the modal is opened.
|
||||
setupKeyboardAccessibility(modal, mappedModules);
|
||||
|
||||
// We want to focus on the first chooser option element as soon as the modal is opened.
|
||||
modal.getRoot().on(ModalEvents.shown, () => {
|
||||
modal.getModal()[0].tabIndex = -1;
|
||||
// We want to focus on the action select when the dialog is closed.
|
||||
modal.getRoot().on(ModalEvents.hidden, () => {
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
modal.getBodyPromise()
|
||||
.then(body => {
|
||||
$(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);
|
||||
});
|
||||
|
||||
modal.show();
|
||||
return modal;
|
||||
})
|
||||
.catch();
|
||||
};
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -322,6 +322,9 @@ define([
|
||||
var contentPromise = null;
|
||||
body.css('overflow', 'hidden');
|
||||
|
||||
// Ensure that the `value` is a jQuery Promise.
|
||||
value = $.when(value);
|
||||
|
||||
if (value.state() == 'pending') {
|
||||
// We're still waiting for the body promise to resolve so
|
||||
// let's show a loading icon.
|
||||
|
||||
@@ -1511,7 +1511,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
*/
|
||||
.modchooser .modal-body {
|
||||
padding: 0;
|
||||
height: 640px;
|
||||
min-height: 640px;
|
||||
overflow-y: auto;
|
||||
|
||||
.loading-icon {
|
||||
@@ -1521,9 +1521,11 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
font-size: 3em;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 5em auto;
|
||||
}
|
||||
}
|
||||
.carousel-item .loading-icon .icon {
|
||||
margin: 5em auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modchoosercontainer.noscroll {
|
||||
@@ -1582,7 +1584,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
background-color: $white;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 640px;
|
||||
min-height: 640px;
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -10653,7 +10653,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
*/
|
||||
.modchooser .modal-body {
|
||||
padding: 0;
|
||||
height: 640px;
|
||||
min-height: 640px;
|
||||
overflow-y: auto; }
|
||||
.modchooser .modal-body .loading-icon {
|
||||
opacity: 1; }
|
||||
@@ -10661,8 +10661,9 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
display: block;
|
||||
font-size: 3em;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 5em auto; }
|
||||
width: 1em; }
|
||||
.modchooser .modal-body .carousel-item .loading-icon .icon {
|
||||
margin: 5em auto; }
|
||||
|
||||
.modchoosercontainer.noscroll {
|
||||
overflow-y: hidden; }
|
||||
@@ -10708,7 +10709,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
background-color: #fff;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 640px; }
|
||||
min-height: 640px; }
|
||||
.modchooser .modal-body .optionsummary .content {
|
||||
overflow-y: auto; }
|
||||
.modchooser .modal-body .optionsummary .content .heading .icon {
|
||||
|
||||
@@ -10861,7 +10861,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
*/
|
||||
.modchooser .modal-body {
|
||||
padding: 0;
|
||||
height: 640px;
|
||||
min-height: 640px;
|
||||
overflow-y: auto; }
|
||||
.modchooser .modal-body .loading-icon {
|
||||
opacity: 1; }
|
||||
@@ -10869,8 +10869,9 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
display: block;
|
||||
font-size: 3em;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 5em auto; }
|
||||
width: 1em; }
|
||||
.modchooser .modal-body .carousel-item .loading-icon .icon {
|
||||
margin: 5em auto; }
|
||||
|
||||
.modchoosercontainer.noscroll {
|
||||
overflow-y: hidden; }
|
||||
@@ -10916,7 +10917,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
|
||||
background-color: #fff;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 640px; }
|
||||
min-height: 640px; }
|
||||
.modchooser .modal-body .optionsummary .content {
|
||||
overflow-y: auto; }
|
||||
.modchooser .modal-body .optionsummary .content .heading .icon {
|
||||
|
||||
Reference in New Issue
Block a user