1 |
efrain |
1 |
{"version":3,"file":"loader.min.js","sources":["../../../src/local/templates/loader.js"],"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 <http://www.gnu.org/licenses/>.\n\nimport $ from 'jquery';\nimport ajax from 'core/ajax';\nimport * as str from 'core/str';\nimport * as config from 'core/config';\nimport mustache from 'core/mustache';\nimport storage from 'core/localstorage';\nimport {getNormalisedComponent} from 'core/utils';\n\n/**\n * Template this.\n *\n * @module core/local/templates/loader\n * @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.3\n */\nexport default class Loader {\n /** @var {String} themeName for the current render */\n currentThemeName = '';\n\n /** @var {Object[]} loadTemplateBuffer - List of templates to be loaded */\n static loadTemplateBuffer = [];\n\n /** @var {Bool} isLoadingTemplates - Whether templates are currently being loaded */\n static isLoadingTemplates = false;\n\n /** @var {Map} templateCache - Cache of already loaded template strings */\n static templateCache = new Map();\n\n /** @var {Promise[]} templatePromises - Cache of already loaded template promises */\n static templatePromises = {};\n\n /** @var {Promise[]} cachePartialPromises - Cache of already loaded template partial promises */\n static cachePartialPromises = [];\n\n /**\n * A helper to get the search key\n *\n * @param {string} theme\n * @param {string} templateName\n * @returns {string}\n */\n static getSearchKey(theme, templateName) {\n return `${theme}/${templateName}`;\n }\n\n /**\n * Load a template.\n *\n * @method getTemplate\n * @param {string} templateName - should consist of the component and the name of the template like this:\n * core/menu (lib/templates/menu.mustache) or\n * tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)\n * @param {string} [themeName=config.theme] - The theme to load the template from\n * @return {Promise} JQuery promise object resolved when the template has been fetched.\n */\n static getTemplate(templateName, themeName = config.theme) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n // If we haven't already seen this template then buffer it.\n const cachedPromise = this.getTemplatePromiseFromCache(searchKey);\n if (cachedPromise) {\n return cachedPromise;\n }\n\n // Check the buffer to see if this template has already been added.\n const existingBufferRecords = this.loadTemplateBuffer.filter((record) => record.searchKey === searchKey);\n if (existingBufferRecords.length) {\n // This template is already in the buffer so just return the existing\n // promise. No need to add it to the buffer again.\n return existingBufferRecords[0].deferred.promise();\n }\n\n // This is the first time this has been requested so let's add it to the buffer\n // to be loaded.\n const parts = templateName.split('/');\n const component = getNormalisedComponent(parts.shift());\n const name = parts.join('/');\n const deferred = $.Deferred();\n\n // Add this template to the buffer to be loaded.\n this.loadTemplateBuffer.push({\n component,\n name,\n theme: themeName,\n searchKey,\n deferred,\n });\n\n // We know there is at least one thing in the buffer so kick off a processing run.\n this.processLoadTemplateBuffer();\n return deferred.promise();\n }\n\n /**\n * Store a template in the cache.\n *\n * @param {string} searchKey\n * @param {string} templateSource\n */\n static setTemplateInCache(searchKey, templateSource) {\n // Cache all of the dependent templates because we'll need them to render\n // the requested template.\n this.templateCache.set(searchKey, templateSource);\n }\n\n /**\n * Fetch a template from the cache.\n *\n * @param {string} searchKey\n * @returns {string}\n */\n static getTemplateFromCache(searchKey) {\n return this.templateCache.get(searchKey);\n }\n\n /**\n * Check whether a template is in the cache.\n *\n * @param {string} searchKey\n * @returns {bool}\n */\n static hasTemplateInCache(searchKey) {\n return this.templateCache.has(searchKey);\n }\n\n /**\n * Prefetch a set of templates without rendering them.\n *\n * @param {Array} templateNames The list of templates to fetch\n * @param {string} themeName\n */\n static prefetchTemplates(templateNames, themeName) {\n templateNames.forEach((templateName) => this.prefetchTemplate(templateName, themeName));\n }\n\n /**\n * Prefetech a sginle template without rendering it.\n *\n * @param {string} templateName\n * @param {string} themeName\n */\n static prefetchTemplate(templateName, themeName) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n // If we haven't already seen this template then buffer it.\n if (this.hasTemplateInCache(searchKey)) {\n return;\n }\n\n // Check the buffer to see if this template has already been added.\n const existingBufferRecords = this.loadTemplateBuffer.filter((record) => record.searchKey === searchKey);\n\n if (existingBufferRecords.length) {\n // This template is already in the buffer so just return the existing promise.\n // No need to add it to the buffer again.\n return;\n }\n\n // This is the first time this has been requested so let's add it to the buffer to be loaded.\n const parts = templateName.split('/');\n const component = getNormalisedComponent(parts.shift());\n const name = parts.join('/');\n\n // Add this template to the buffer to be loaded.\n this.loadTemplateBuffer.push({\n component,\n name,\n theme: themeName,\n searchKey,\n deferred: $.Deferred(),\n });\n\n this.processLoadTemplateBuffer();\n }\n\n /**\n * Load a partial from the cache or ajax.\n *\n * @method partialHelper\n * @param {string} name The partial name to load.\n * @param {string} [themeName = config.theme] The theme to load the partial from.\n * @return {string}\n */\n static partialHelper(name, themeName = config.theme) {\n const searchKey = this.getSearchKey(themeName, name);\n\n if (!this.hasTemplateInCache(searchKey)) {\n new Error(`Failed to pre-fetch the template: ${name}`);\n }\n return this.getTemplateFromCache(searchKey);\n }\n\n /**\n * Scan a template source for partial tags and return a list of the found partials.\n *\n * @method scanForPartials\n * @param {string} templateSource - source template to scan.\n * @return {Array} List of partials.\n */\n static scanForPartials(templateSource) {\n const tokens = mustache.parse(templateSource);\n const partials = [];\n\n const findPartial = (tokens, partials) => {\n let i;\n for (i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token[0] == '>' || token[0] == '<') {\n partials.push(token[1]);\n }\n if (token.length > 4) {\n findPartial(token[4], partials);\n }\n }\n };\n\n findPartial(tokens, partials);\n\n return partials;\n }\n\n /**\n * Load a template and scan it for partials. Recursively fetch the partials.\n *\n * @method cachePartials\n * @param {string} templateName - should consist of the component and the name of the template like this:\n * core/menu (lib/templates/menu.mustache) or\n * tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)\n * @param {string} [themeName=config.theme]\n * @param {Array} parentage - A list of requested partials in this render chain.\n * @return {Promise} JQuery promise object resolved when all partials are in the cache.\n */\n static cachePartials(templateName, themeName = config.theme, parentage = []) {\n const searchKey = this.getSearchKey(themeName, templateName);\n\n if (searchKey in this.cachePartialPromises) {\n return this.cachePartialPromises[searchKey];\n }\n\n // This promise will not be resolved until all child partials are also resolved and ready.\n // We create it here to allow us to check for recursive inclusion of templates.\n // Keep track of the requested partials in this chain.\n if (!parentage.length) {\n parentage.push(searchKey);\n }\n\n this.cachePartialPromises[searchKey] = $.Deferred();\n this._cachePartials(templateName, themeName, parentage).catch((error) => {\n this.cachePartialPromises[searchKey].reject(error);\n });\n\n return this.cachePartialPromises[searchKey];\n }\n\n /**\n * Cache the template partials for the specified template.\n *\n * @param {string} templateName\n * @param {string} themeName\n * @param {array} parentage\n * @returns {promise<string>}\n */\n static async _cachePartials(templateName, themeName, parentage) {\n const searchKey = this.getSearchKey(themeName, templateName);\n const templateSource = await this.getTemplate(templateName, themeName);\n const partials = this.scanForPartials(templateSource);\n const uniquePartials = partials.filter((partialName) => {\n // Check for recursion.\n if (parentage.indexOf(`${themeName}/${partialName}`) >= 0) {\n // Ignore templates which include a parent template already requested in the current chain.\n return false;\n }\n\n // Ignore templates that include themselves.\n return partialName !== templateName;\n });\n\n // Fetch any partial which has not already been fetched.\n const fetchThemAll = uniquePartials.map((partialName) => {\n parentage.push(`${themeName}/${partialName}`);\n return this.cachePartials(partialName, themeName, parentage);\n });\n\n await Promise.all(fetchThemAll);\n return this.cachePartialPromises[searchKey].resolve(templateSource);\n }\n\n /**\n * Take all of the templates waiting in the buffer and load them from the server\n * or from the cache.\n *\n * All of the templates that need to be loaded from the server will be batched up\n * and sent in a single network request.\n */\n static processLoadTemplateBuffer() {\n if (!this.loadTemplateBuffer.length) {\n return;\n }\n\n if (this.isLoadingTemplates) {\n return;\n }\n\n this.isLoadingTemplates = true;\n // Grab any templates waiting in the buffer.\n const templatesToLoad = this.loadTemplateBuffer.slice();\n // This will be resolved with the list of promises for the server request.\n const serverRequestsDeferred = $.Deferred();\n const requests = [];\n // Get a list of promises for each of the templates we need to load.\n const templatePromises = templatesToLoad.map((templateData) => {\n const component = getNormalisedComponent(templateData.component);\n const name = templateData.name;\n const searchKey = templateData.searchKey;\n const theme = templateData.theme;\n const templateDeferred = templateData.deferred;\n let promise = null;\n\n // Double check to see if this template happened to have landed in the\n // cache as a dependency of an earlier template.\n if (this.hasTemplateInCache(searchKey)) {\n // We've seen this template so immediately resolve the existing promise.\n promise = this.getTemplatePromiseFromCache(searchKey);\n } else {\n // We haven't seen this template yet so we need to request it from\n // the server.\n requests.push({\n methodname: 'core_output_load_template_with_dependencies',\n args: {\n component,\n template: name,\n themename: theme,\n lang: config.language,\n }\n });\n // Remember the index in the requests list for this template so that\n // we can get the appropriate promise back.\n const index = requests.length - 1;\n\n // The server deferred will be resolved with a list of all of the promises\n // that were sent in the order that they were added to the requests array.\n promise = serverRequestsDeferred.promise()\n .then((promises) => {\n // The promise for this template will be the one that matches the index\n // for it's entry in the requests array.\n //\n // Make sure the promise is added to the promises cache for this template\n // search key so that we don't request it again.\n templatePromises[searchKey] = promises[index].then((response) => {\n // Process all of the template dependencies for this template and add\n // them to the caches so that we don't request them again later.\n response.templates.forEach((data) => {\n data.component = getNormalisedComponent(data.component);\n const tempSearchKey = this.getSearchKey(\n theme,\n [data.component, data.name].join('/'),\n );\n\n // Cache all of the dependent templates because we'll need them to render\n // the requested template.\n this.setTemplateInCache(tempSearchKey, data.value);\n\n if (config.templaterev > 0) {\n // The template cache is enabled - set the value there.\n storage.set(`core_template/${config.templaterev}:${tempSearchKey}`, data.value);\n }\n });\n\n if (response.strings.length) {\n // If we have strings that the template needs then warm the string cache\n // with them now so that we don't need to re-fetch them.\n str.cache_strings(response.strings.map(({component, name, value}) => ({\n component: getNormalisedComponent(component),\n key: name,\n value,\n })));\n }\n\n // Return the original template source that the user requested.\n if (this.hasTemplateInCache(searchKey)) {\n return this.getTemplateFromCache(searchKey);\n }\n\n return null;\n });\n\n return templatePromises[searchKey];\n });\n }\n\n return promise\n // When we've successfully loaded the template then resolve the deferred\n // in the buffer so that all of the calling code can proceed.\n .then((source) => templateDeferred.resolve(source))\n .catch((error) => {\n // If there was an error loading the template then reject the deferred\n // in the buffer so that all of the calling code can proceed.\n templateDeferred.reject(error);\n // Rethrow for anyone else listening.\n throw error;\n });\n });\n\n if (requests.length) {\n // We have requests to send so resolve the deferred with the promises.\n serverRequestsDeferred.resolve(ajax.call(requests, true, false, false, 0, config.templaterev));\n } else {\n // Nothing to load so we can resolve our deferred.\n serverRequestsDeferred.resolve();\n }\n\n // Once we've finished loading all of the templates then recurse to process\n // any templates that may have been added to the buffer in the time that we\n // were fetching.\n $.when.apply(null, templatePromises)\n .then(() => {\n // Remove the templates we've loaded from the buffer.\n this.loadTemplateBuffer.splice(0, templatesToLoad.length);\n this.isLoadingTemplates = false;\n this.processLoadTemplateBuffer();\n return;\n })\n .catch(() => {\n // Remove the templates we've loaded from the buffer.\n this.loadTemplateBuffer.splice(0, templatesToLoad.length);\n this.isLoadingTemplates = false;\n this.processLoadTemplateBuffer();\n });\n }\n\n /**\n * Search the various caches for a template promise for the given search key.\n * The search key should be in the format <theme>/<component>/<template> e.g. boost/core/modal.\n *\n * If the template is found in any of the caches it will populate the other caches with\n * the same data as well.\n *\n * @param {String} searchKey The template search key in the format <theme>/<component>/<template> e.g. boost/core/modal\n * @returns {Object|null} jQuery promise resolved with the template source\n */\n static getTemplatePromiseFromCache(searchKey) {\n // First try the cache of promises.\n if (searchKey in this.templatePromises) {\n return this.templatePromises[searchKey];\n }\n\n // Check the module cache.\n if (this.hasTemplateInCache(searchKey)) {\n const templateSource = this.getTemplateFromCache(searchKey);\n // Add this to the promises cache for future.\n this.templatePromises[searchKey] = $.Deferred().resolve(templateSource).promise();\n return this.templatePromises[searchKey];\n }\n\n if (config.templaterev <= 0) {\n // Template caching is disabled. Do not store in persistent storage.\n return null;\n }\n\n // Now try local storage.\n const cached = storage.get(`core_template/${config.templaterev}:${searchKey}`);\n if (cached) {\n // Add this to the module cache for future.\n this.setTemplateInCache(searchKey, cached);\n\n // Add to the promises cache for future.\n this.templatePromises[searchKey] = $.Deferred().resolve(cached).promise();\n return this.templatePromises[searchKey];\n }\n\n return null;\n }\n}\n"],"names":["Loader","theme","templateName","themeName","config","searchKey","this","getSearchKey","cachedPromise","getTemplatePromiseFromCache","existingBufferRecords","loadTemplateBuffer","filter","record","length","deferred","promise","parts","split","component","shift","name","join","$","Deferred","push","processLoadTemplateBuffer","templateSource","templateCache","set","get","has","templateNames","forEach","prefetchTemplate","hasTemplateInCache","Error","getTemplateFromCache","tokens","mustache","parse","partials","findPartial","i","token","parentage","cachePartialPromises","_cachePartials","catch","error","reject","getTemplate","fetchThemAll","scanForPartials","partialName","indexOf","map","cachePartials","Promise","all","resolve","isLoadingTemplates","templatesToLoad","slice","serverRequestsDeferred","requests","templatePromises","templateData","templateDeferred","methodname","args","template","themename","lang","language","index","then","promises","response","templates","data","tempSearchKey","setTemplateInCache","value","templaterev","strings","str","cache_strings","_ref","key","source","ajax","call","when","apply","splice","cached","storage","Map"],"mappings":"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"}
|