// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Prefetch module to help lazily load content for use on the current page.
*
* @module core/prefetch
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @example <caption>Pre-fetching a set of strings to use later</caption>
*
* import prefetch from 'core/prefetch';
*
* // A single string prefetch.
* prefetch.prefetchString('error', 'cannotfindteacher');
*
* // Prefetch multiple strings in the same component.
* prefetch.prefetchStrings('core', [
* 'yes',
* 'no',
* ]);
*
* // Use the strings.
* import {get_string as getString, get_strings as getStrings} from 'core/str';
* getString('cannotfindteacher', 'error')
* .then(str => {
* window.console.log(str); // Cannot find teacher
* })
* .catch();
* getStrings([
* {
* key: 'cannotfindteacher',
* component: 'error',
* },
* {
* key: 'yes',
* component: 'core',
* },
* {
* key: 'no',
* component: 'core',
* },
* ])
* .then((cannotFindTeacher, yes, no) => {
* window.console.log(cannotFindTeacher); // Cannot find teacher
* window.console.log(yes); // Yes
* window.console.log(no); // No
* })
* .catch();
*/
import Config from 'core/config';
// Keep track of whether the initial prefetch has occurred.
let initialPrefetchComplete = false;
// Prefetch templates.
let templateList = [];
// Prefetch strings.
let stringList = {};
let prefetchTimer;
/**
* Fetch all queued items in the queue.
*
* Should only be called via processQueue.
* @private
*/
const fetchQueue = () => {
// Prefetch templates.
if (templateList) {
const templatesToLoad = templateList.slice();
templateList = [];
import('core/templates')
.then(Templates => Templates.prefetchTemplates(templatesToLoad))
.catch();
}
// Prefetch strings.
const mappedStringsToFetch = stringList;
stringList = {};
const stringsToFetch = [];
Object.keys(mappedStringsToFetch).forEach(component => {
stringsToFetch.push(...mappedStringsToFetch[component].map(key => {
return {component, key};
}));
});
if (stringsToFetch) {
import('core/str')
.then(Str => Str.get_strings(stringsToFetch))
.catch();
}
};
/**
* Process the prefetch queues as required.
*
* The initial call will queue the first fetch after a delay.
* Subsequent fetches are immediate.
*
* @private
*/
const processQueue = () => {
if (prefetchTimer) {
// There is a live prefetch timer. The initial prefetch has been scheduled but is not complete.
return;
}
// The initial prefetch has compelted. Just queue as normal.
if (initialPrefetchComplete) {
fetchQueue();
return;
}
// Queue the initial prefetch in a short while.
prefetchTimer = setTimeout(() => {
initialPrefetchComplete = true;
prefetchTimer = null;
// Ensure that the icon system is loaded.
// This can be quite slow and delay UI interactions if it is loaded on demand.
import(Config.iconsystemmodule)
.then(IconSystem => {
const iconSystem = new IconSystem();
prefetchTemplate(iconSystem.getTemplateName());
return iconSystem;
})
.then(iconSystem => {
fetchQueue();
iconSystem.init();
return;
})
.catch();
}, 500);
};
/**
* Add a set of templates to the prefetch queue.
*
* @param {Array} templatesNames A list of the template names to fetch
* @static
*/
const prefetchTemplates = templatesNames => {
templateList = templateList.concat(templatesNames);
processQueue();
};
/**
* Add a single template to the prefetch queue.
*
* @param {String} templateName The template names to fetch
* @static
*/
const prefetchTemplate = templateName => {
prefetchTemplates([templateName]);
};
/**
* Add a set of strings from the same component to the prefetch queue.
*
* @param {String} component The component that all of the strings belongs to
* @param {String[]} keys An array of string identifiers.
* @static
*/
const prefetchStrings = (component, keys) => {
if (!stringList[component]) {
stringList[component] = [];
}
stringList[component] = stringList[component].concat(keys);
processQueue();
};
/**
* Add a single string to the prefetch queue.
*
* @param {String} component The component that the string belongs to
* @param {String} key The string identifier
* @static
*/
const prefetchString = (component, key) => {
if (!stringList[component]) {
stringList[component] = [];
}
stringList[component].push(key);
processQueue();
};
// Prefetch some commonly-used templates.
prefetchTemplates([].concat(
['core/loading'],
['core/modal'],
['core/modal_backdrop'],
));
// And some commonly used strings.
prefetchStrings('core', [
'cancel',
'closebuttontitle',
'loading',
'savechanges',
]);
prefetchStrings('core_form', [
'showless',
'showmore',
]);
export default {
prefetchTemplate,
prefetchTemplates,
prefetchString,
prefetchStrings,
};