mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 00:46:50 +02:00
MDL-53667 templates: Improve JS string handling in templates
The native String.replace method in extremely slow when we are dealing with a large string and large quantity of strings to replace. This new solution walks through the string looking for placeholders to replace.
This commit is contained in:
parent
e912927d7b
commit
29879f8f4c
2 changed files with 88 additions and 33 deletions
2
lib/amd/build/templates.min.js
vendored
2
lib/amd/build/templates.min.js
vendored
|
@ -1 +1 @@
|
||||||
define(["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/event","core/yui"],function(a,b,c,d,e,f,g,h,i,j){var k={},l=[],m=[],n=1,o="",p=function(a,d){var e=b.Deferred(),f=a.split("/"),g=f.shift(),i=f.shift(),j=o+"/"+a;if(j in k)return e.resolve(k[j]),e.promise();var l=h.get("core_template/"+j);if(l)return e.resolve(l),k[j]=l,e.promise();var m=c.call([{methodname:"core_output_load_template",args:{component:g,template:i,themename:o}}],d,!1);return m[0].done(function(a){h.set("core_template/"+j,a),k[j]=a,e.resolve(a)}).fail(function(a){e.reject(a)}),e.promise()},q=function(a){var b="";return p(a,!1).done(function(a){b=a}).fail(e.exception),b},r=function(b,c){var d,e=b.split(","),g="",h="",i="";e.length>0&&(g=e.shift().trim()),e.length>0&&(h=e.shift().trim()),e.length>0&&(i=e.join(",").trim());var j=f.imageUrl(g,h),l={attributes:[{name:"src",value:j},{name:"alt",value:c(i)},{name:"class",value:"smallicon"}]},m=k[o+"/core/pix_icon"];return d=a.render(m,l,q),d.trim()},s=function(a,b){return m.push(b(a,this)),""},t=function(a,b){var c=a.split(","),d="",e="",f="";c.length>0&&(d=c.shift().trim()),c.length>0&&(e=c.shift().trim()),c.length>0&&(f=c.join(",").trim()),""!==f&&(f=b(f,this)),0===f.indexOf("{")&&0!==f.indexOf("{{")&&(f=JSON.parse(f));var g=l.length;return l.push({key:d,component:e,param:f}),"{{_s"+g+"}}"},u=function(a,b){var c=b(a.trim(),this);return c=c.replace('"','\\"').replace(/([\{\}]{2,3})/g,"{{=<% %>=}}$1<%={{ }}=%>"),'"'+c+'"'},v=function(a,b){o=b,l=[],m=[],a.uniqid=n++,a.str=function(){return t},a.pix=function(){return r},a.js=function(){return s},a.quote=function(){return u},a.globals={config:g},a.currentTheme=b},w=function(a){var b="";m.length>0&&(b=m.join(";\n"));var c=0;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);return b},x=function(c,e,f){var g=b.Deferred();o=f;var h=p("core/pix_icon",!0);return h.done(function(){v(e,f);var b="";try{b=a.render(c,e,q)}catch(h){g.reject(h)}l.length>0?d.get_strings(l).done(function(a){var c;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);g.resolve(b.trim(),w(a))}).fail(function(a){g.reject(a)}):g.resolve(b.trim(),w([]))}).fail(function(a){g.reject(a)}),g.promise()},y=function(a){if(""!==a.trim()){var c=b("<script>").attr("type","text/javascript").html(a);b("head").append(c)}},z=function(a,c,d,e){var f=b(a);if(f.length){var g=b(c),h=null;e?(h=new j.NodeList(f.children().get()),h.destroy(!0),f.empty(),f.append(g)):(h=new j.NodeList(f.get()),h.destroy(!0),f.replaceWith(g)),y(d),i.notifyFilterContentUpdated(g)}};return{render:function(a,c,d){var e=b.Deferred();"undefined"==typeof d&&(d=g.theme),o=d;var f=p(a,!0);return f.done(function(a){var b=x(a,c,d);b.done(function(a,b){e.resolve(a,b)}).fail(function(a){e.reject(a)})}).fail(function(a){e.reject(a)}),e.promise()},runTemplateJS:y,replaceNodeContents:function(a,b,c){return z(a,b,c,!0)},replaceNode:function(a,b,c){return z(a,b,c,!1)}}});
|
define(["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/event","core/yui","core/log"],function(a,b,c,d,e,f,g,h,i,j,k){var l={},m=[],n=[],o=1,p="",q=function(a,d){var e=b.Deferred(),f=a.split("/"),g=f.shift(),i=f.shift(),j=p+"/"+a;if(j in l)return e.resolve(l[j]),e.promise();var k=h.get("core_template/"+j);if(k)return e.resolve(k),l[j]=k,e.promise();var m=c.call([{methodname:"core_output_load_template",args:{component:g,template:i,themename:p}}],d,!1);return m[0].done(function(a){h.set("core_template/"+j,a),l[j]=a,e.resolve(a)}).fail(function(a){e.reject(a)}),e.promise()},r=function(a){var b="";return q(a,!1).done(function(a){b=a}).fail(e.exception),b},s=function(b,c){var d,e=b.split(","),g="",h="",i="";e.length>0&&(g=e.shift().trim()),e.length>0&&(h=e.shift().trim()),e.length>0&&(i=e.join(",").trim());var j=f.imageUrl(g,h),k={attributes:[{name:"src",value:j},{name:"alt",value:c(i)},{name:"class",value:"smallicon"}]},m=l[p+"/core/pix_icon"];return d=a.render(m,k,r),d.trim()},t=function(a,b){return n.push(b(a,this)),""},u=function(a,b){var c=a.split(","),d="",e="",f="";c.length>0&&(d=c.shift().trim()),c.length>0&&(e=c.shift().trim()),c.length>0&&(f=c.join(",").trim()),""!==f&&(f=b(f,this)),0===f.indexOf("{")&&0!==f.indexOf("{{")&&(f=JSON.parse(f));var g=m.length;return m.push({key:d,component:e,param:f}),"{{_s"+g+"}}"},v=function(a,b){var c=b(a.trim(),this);return c=c.replace('"','\\"').replace(/([\{\}]{2,3})/g,"{{=<% %>=}}$1<%={{ }}=%>"),'"'+c+'"'},w=function(a,b){p=b,m=[],n=[],a.uniqid=o++,a.str=function(){return u},a.pix=function(){return s},a.js=function(){return t},a.quote=function(){return v},a.globals={config:g},a.currentTheme=b},x=function(a){var b="";return n.length>0&&(b=n.join(";\n")),y(b,a)},y=function(a,b){var c,d,e,f,g,h,i=/{{_s\d+}}/;do{for(c="",d=a.search(i);d>-1;){c+=a.substring(0,d),a=a.substr(d),e="",f=4,g=a.substr(f,1);do e+=g,f++,g=a.substr(f,1);while("}"!=g);h=b[parseInt(e,10)],"undefined"==typeof h&&(k.debug("Could not find string for pattern {{_s"+e+"}}."),h=""),c+=h,a=a.substr(6+e.length),d=a.search(i)}a=c+a,d=a.search(i)}while(d>-1);return a},z=function(c,e,f){var g=b.Deferred();p=f;var h=q("core/pix_icon",!0);return h.done(function(){w(e,f);var b="";try{b=a.render(c,e,r)}catch(h){g.reject(h)}m.length>0?d.get_strings(m).then(function(a){b=y(b,a),g.resolve(b,x(a))}).fail(g.reject):g.resolve(b.trim(),x([]))}).fail(g.reject),g.promise()},A=function(a){if(""!==a.trim()){var c=b("<script>").attr("type","text/javascript").html(a);b("head").append(c)}},B=function(a,c,d,e){var f=b(a);if(f.length){var g=b(c),h=null;e?(h=new j.NodeList(f.children().get()),h.destroy(!0),f.empty(),f.append(g)):(h=new j.NodeList(f.get()),h.destroy(!0),f.replaceWith(g)),A(d),i.notifyFilterContentUpdated(g)}};return{render:function(a,c,d){var e=b.Deferred();"undefined"==typeof d&&(d=g.theme),p=d;var f=q(a,!0);return f.done(function(a){var b=z(a,c,d);b.done(function(a,b){e.resolve(a,b)}).fail(function(a){e.reject(a)})}).fail(function(a){e.reject(a)}),e.promise()},runTemplateJS:A,replaceNodeContents:function(a,b,c){return B(a,b,c,!0)},replaceNode:function(a,b,c){return B(a,b,c,!1)}}});
|
|
@ -32,9 +32,10 @@ define([ 'core/mustache',
|
||||||
'core/config',
|
'core/config',
|
||||||
'core/localstorage',
|
'core/localstorage',
|
||||||
'core/event',
|
'core/event',
|
||||||
'core/yui'
|
'core/yui',
|
||||||
|
'core/log'
|
||||||
],
|
],
|
||||||
function(mustache, $, ajax, str, notification, coreurl, config, storage, event, Y) {
|
function(mustache, $, ajax, str, notification, coreurl, config, storage, event, Y, Log) {
|
||||||
|
|
||||||
// Private variables and functions.
|
// Private variables and functions.
|
||||||
|
|
||||||
|
@ -280,13 +281,77 @@ define([ 'core/mustache',
|
||||||
js = requiredJS.join(";\n");
|
js = requiredJS.join(";\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < strings.length; i++) {
|
|
||||||
js = js.replace('{{_s' + i + '}}', strings[i]);
|
|
||||||
}
|
|
||||||
// Re-render to get the final strings.
|
// Re-render to get the final strings.
|
||||||
return js;
|
return treatStringsInContent(js, strings);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat strings in content.
|
||||||
|
*
|
||||||
|
* The purpose of this method is to replace the placeholders found in a string
|
||||||
|
* with the their respective translated strings.
|
||||||
|
*
|
||||||
|
* Previously we were relying on String.replace() but the complexity increased with
|
||||||
|
* the numbers of strings to replace. Now we manually walk the string and stop at each
|
||||||
|
* placeholder we find, only then we replace it. Most of the time we will
|
||||||
|
* replace all the placeholders in a single run, at times we will need a few
|
||||||
|
* more runs when placeholders are replaced with strings that contain placeholders
|
||||||
|
* themselves.
|
||||||
|
*
|
||||||
|
* @param {String} content The content in which string placeholders are to be found.
|
||||||
|
* @param {Array} strings The strings to replace with.
|
||||||
|
* @return {String} The treated content.
|
||||||
|
*/
|
||||||
|
var treatStringsInContent = function(content, strings) {
|
||||||
|
var pattern = /{{_s\d+}}/,
|
||||||
|
treated,
|
||||||
|
index,
|
||||||
|
strIndex,
|
||||||
|
walker,
|
||||||
|
char,
|
||||||
|
strFinal;
|
||||||
|
|
||||||
|
do {
|
||||||
|
treated = '';
|
||||||
|
index = content.search(pattern);
|
||||||
|
while (index > -1) {
|
||||||
|
|
||||||
|
// Copy the part prior to the placeholder to the treated string.
|
||||||
|
treated += content.substring(0, index);
|
||||||
|
content = content.substr(index);
|
||||||
|
strIndex = '';
|
||||||
|
walker = 4; // 4 is the length of '{{_s'.
|
||||||
|
|
||||||
|
// Walk the characters to manually extract the index of the string from the placeholder.
|
||||||
|
char = content.substr(walker, 1);
|
||||||
|
do {
|
||||||
|
strIndex += char;
|
||||||
|
walker++;
|
||||||
|
char = content.substr(walker, 1);
|
||||||
|
} while (char != '}');
|
||||||
|
|
||||||
|
// Get the string, add it to the treated result, and remove the placeholder from the content to treat.
|
||||||
|
strFinal = strings[parseInt(strIndex, 10)];
|
||||||
|
if (typeof strFinal === 'undefined') {
|
||||||
|
Log.debug('Could not find string for pattern {{_s' + strIndex + '}}.');
|
||||||
|
strFinal = '';
|
||||||
|
}
|
||||||
|
treated += strFinal;
|
||||||
|
content = content.substr(6 + strIndex.length); // 6 is the length of the placeholder without the index: '{{_s}}'.
|
||||||
|
|
||||||
|
// Find the next placeholder.
|
||||||
|
index = content.search(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The content becomes the treated part with the rest of the content.
|
||||||
|
content = treated + content;
|
||||||
|
|
||||||
|
// Check if we need to walk the content again, in case strings contained placeholders.
|
||||||
|
index = content.search(pattern);
|
||||||
|
|
||||||
|
} while (index > -1);
|
||||||
|
|
||||||
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -318,35 +383,25 @@ define([ 'core/mustache',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredStrings.length > 0) {
|
if (requiredStrings.length > 0) {
|
||||||
str.get_strings(requiredStrings).done(
|
str.get_strings(requiredStrings)
|
||||||
function(strings) {
|
.then(function(strings) {
|
||||||
var i;
|
|
||||||
|
|
||||||
// Why do we not do another call the render here?
|
// Why do we not do another call the render here?
|
||||||
//
|
//
|
||||||
// Because that would expose DOS holes. E.g.
|
// Because that would expose DOS holes. E.g.
|
||||||
// I create an assignment called "{{fish" which
|
// I create an assignment called "{{fish" which
|
||||||
// would get inserted in the template in the first pass
|
// would get inserted in the template in the first pass
|
||||||
// and cause the template to die on the second pass (unbalanced).
|
// and cause the template to die on the second pass (unbalanced).
|
||||||
for (i = 0; i < strings.length; i++) {
|
|
||||||
result = result.replace('{{_s' + i + '}}', strings[i]);
|
result = treatStringsInContent(result, strings);
|
||||||
}
|
deferred.resolve(result, getJS(strings));
|
||||||
deferred.resolve(result.trim(), getJS(strings));
|
})
|
||||||
}
|
.fail(deferred.reject);
|
||||||
).fail(
|
|
||||||
function(ex) {
|
|
||||||
deferred.reject(ex);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
deferred.resolve(result.trim(), getJS([]));
|
deferred.resolve(result.trim(), getJS([]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).fail(
|
).fail(deferred.reject);
|
||||||
function(ex) {
|
|
||||||
deferred.reject(ex);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise();
|
return deferred.promise();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue