MDL-68524 js: Add prefetch module
[moodle.git] / lib / amd / src / prefetch.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Prefetch module to help lazily load content for use on the current page.
18  *
19  * @module     core/prefetch
20  * @class      prefetch
21  * @package    core
22  * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
25 import Config from 'core/config';
27 // Keep track of whether the initial prefetch has occurred.
28 let initialPrefetchComplete = false;
30 // Prefetch templates.
31 let templateList = [];
33 // Prefetch strings.
34 let stringList = {};
36 let prefetchTimer;
38 /**
39  * Fetch all queued items in the queue.
40  *
41  * Should only be called via processQueue.
42  */
43 const fetchQueue = () => {
44     // Prefetch templates.
45     if (templateList) {
46         const templatesToLoad = templateList.slice();
47         templateList = [];
48         import('core/templates')
49         .then(Templates => Templates.prefetchTemplates(templatesToLoad))
50         .catch();
51     }
53     // Prefetch strings.
54     const mappedStringsToFetch = stringList;
55     stringList = {};
57     const stringsToFetch = [];
58     Object.keys(mappedStringsToFetch).forEach(component => {
59         stringsToFetch.push(...mappedStringsToFetch[component].map(key => {
60             return {component, key};
61         }));
62     });
64     if (stringsToFetch) {
65         import('core/str')
66         .then(Str => Str.get_strings(stringsToFetch))
67         .catch();
68     }
69 };
71 /**
72  * Process the prefetch queues as required.
73  *
74  * The initial call will queue the first fetch after a delay.
75  * Subsequent fetches are immediate.
76  */
77 const processQueue = () => {
78     if (Config.jsrev <= 0) {
79         // No point pre-fetching when cachejs is disabled as we do not store anything in the cache anyway.
80         return;
81     }
83     if (prefetchTimer) {
84         // There is a live prefetch timer. The initial prefetch has been scheduled but is not complete.
85         return;
86     }
88     // The initial prefetch has compelted. Just queue as normal.
89     if (initialPrefetchComplete) {
90         fetchQueue();
92         return;
93     }
95     // Queue the initial prefetch in a short while.
96     prefetchTimer = setTimeout(() => {
97         initialPrefetchComplete = true;
98         prefetchTimer = null;
100         // Ensure that the icon system is loaded.
101         // This can be quite slow and delay UI interactions if it is loaded on demand.
102         import(Config.iconsystemmodule)
103         .then(IconSystem => {
104             const iconSystem = new IconSystem();
105             prefetchTemplate(iconSystem.getTemplateName());
107             return iconSystem;
108         })
109         .then(iconSystem => {
110             fetchQueue();
111             iconSystem.init();
113             return;
114         })
115         .catch();
116     }, 500);
117 };
119 /**
120  * Add a set of templates to the prefetch queue.
121  *
122  * @param {Array} templatesNames
123  */
124 const prefetchTemplates = templatesNames => {
125     templateList = templateList.concat(templatesNames);
127     processQueue();
128 };
130 /**
131  * Add a single template to the prefetch queue.
132  *
133  * @param {String} templateName
134  * @returns {undefined}
135  */
136 const prefetchTemplate = templateName => prefetchTemplates([templateName]);
138 /**
139  * Add a set of strings from the same component to the prefetch queue.
140  *
141  * @param {String} component
142  * @param {String[]} keys
143  */
144 const prefetchStrings = (component, keys) => {
145     if (!stringList[component]) {
146         stringList[component] = [];
147     }
149     stringList[component] = stringList[component].concat(keys);
151     processQueue();
152 };
154 /**
155  * Add a single string to the prefetch queue.
156  *
157  * @param {String} component
158  * @param {String} key
159  */
160 const prefetchString = (component, key) => {
161     if (!stringList[component]) {
162         stringList[component] = [];
163     }
165     stringList[component].push(key);
167     processQueue();
168 };
170 // Prefetch some commonly-used templates.
171 prefetchTemplates([].concat(
172     ['core/loading'],
173     ['core/modal'],
174     ['core/modal_backdrop'],
175 ));
177 // And some commonly used strings.
178 prefetchStrings('core', [
179     'cancel',
180     'closebuttontitle',
181     'loading',
182     'savechanges',
183 ]);
184 prefetchStrings('core_form', [
185     'showless',
186     'showmore',
187 ]);
189 export default {
190     prefetchTemplate,
191     prefetchTemplates,
192     prefetchString,
193     prefetchStrings,
194 };