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);
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);
+
+ // 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);
- const sectionModal = await modalBuilder(builtModuleData);
- ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData, favouriteFunction);
+ ChooserDialogue.displayChooser(
+ sectionModal,
+ builtModuleData,
+ partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),
+ );
+
+ bodyPromiseResolver(await Templates.render(
+ 'core_course/activitychooser',
+ templateDataBuilder(builtModuleData)
+ ));
}
});
});
*
* @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) => {
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
*
* 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;
});
};
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);
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);
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).
*
* 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) => {
});
// 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;
-
- 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);
- });
+ // We want to focus on the action select when the dialog is closed.
+ modal.getRoot().on(ModalEvents.hidden, () => {
+ modal.destroy();
+ });
- modal.show();
+ return modal;
+ })
+ .catch();
};
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.
*/
.modchooser .modal-body {
padding: 0;
- height: 640px;
+ min-height: 640px;
overflow-y: auto;
.loading-icon {
font-size: 3em;
height: 1em;
width: 1em;
- margin: 5em auto;
}
}
+ .carousel-item .loading-icon .icon {
+ margin: 5em auto;
+ }
}
.modchoosercontainer.noscroll {
background-color: $white;
overflow-x: hidden;
overflow-y: auto;
- height: 640px;
+ min-height: 640px;
.content {
overflow-y: auto;
*/
.modchooser .modal-body {
padding: 0;
- height: 640px;
+ min-height: 640px;
overflow-y: auto; }
.modchooser .modal-body .loading-icon {
opacity: 1; }
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; }
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 {
*/
.modchooser .modal-body {
padding: 0;
- height: 640px;
+ min-height: 640px;
overflow-y: auto; }
.modchooser .modal-body .loading-icon {
opacity: 1; }
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; }
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 {