App/client/template.js

272 lines
11 KiB
JavaScript

class TemplateEngine
{
constructor(tpl, adminData) {
this.tpl = tpl;
this.adminData = adminData;
this.modeFallback = {
":EDIT": ":ADMIN",
":ADMIN": "",
"": null
};
}
Render(content, marker, mode = "") {
return this.render(marker, content, this.extractSection(this.tpl, marker, mode), mode);
}
render(sections, content, tpl, mode = "") {
// content has to be an object of `marker` => `data`
// data can be a primitive, an array, or an object
// Boolean corresponds to a conditional section, call replaceCondition
// String and number correspond to a simple marker, call replaceMarker for it
// Array and object always correspond to a section
// Object can be handed recursively to a new instance of render. Be sure to extract the correct section of tpl though
// If data is an array, iterate over it and hand every entry to a new instance like with object. Concatenate the results
if (typeof(content)!="object" || tpl=="")
return "";
var ret = tpl;
for (var marker in content) {
var sectionMarker = (sections!="" ? sections+"."+marker : marker);
var type = typeof(content[marker]);
if (mode==":EDIT" && sections.indexOf("MAIN")!=-1 && typeof(this.adminData[marker])!="undefined") {
var data = this.adminData[marker];
var admin_tpl = this.extractSection(ret, sectionMarker, mode);
var admin_ret = "";
for (var i in data) {
if (content[marker]==null) {
data[i].SELECTED = data[i].ID=="__NULL__";
} else if (Array.isArray(content[marker])) {
var selected = false;
for (var j in content[marker]) {
if (data[i].ID==content[marker][j].ID) {
selected = true;
}
}
data[i].SELECTED = selected;
} else {
data[i].SELECTED = data[i].ID==content[marker].ID;
}
admin_ret+= this.render(sectionMarker, data[i], admin_tpl, "");
}
ret = this.replaceSection(ret, sectionMarker, admin_ret, "");
} else {
if (content[marker]==null) {
ret = this.replaceSection(ret, sectionMarker, "", mode);
ret = this.replaceMarker(ret, sectionMarker, "__NULL__", mode);
} if (type=="boolean") {
ret = this.replaceCondition(ret, sectionMarker, content[marker], mode);
} else if (type=="string" && "__CASE__:"==content[marker].substr(0, 9)) {
ret = this.replaceSwitch(ret, sectionMarker, content[marker].split(":"), mode);
} else if (type=="string" || type=="number") {
ret = this.replaceMarker(ret, sectionMarker, content[marker], mode);
} else if (type=="object" && Array.isArray(content[marker])) {
var arr_tpl = this.extractSection(ret, sectionMarker, mode);
var arr_ret = "";
for (var ind in content[marker]) {
arr_ret+= this.render(sectionMarker, content[marker][ind], arr_tpl, mode);
}
ret = this.replaceSection(ret, sectionMarker, arr_ret, mode);
} else if (type=="object") { // Hopefully a JSON
ret = this.replaceSection(ret, sectionMarker, this.render(sectionMarker, content[marker], this.extractSection(ret, sectionMarker, mode), mode), mode);
}
}
}
return ret;
}
makeMarker(marker) {
return "###" + marker + "###";
}
makeSectionMarker(marker, pos) { //creates START or END marker of section
return "<!-- " + marker + " " + pos + " -->";
}
findMarkerModes(tpl, section) {
var ret = [];
var sectionMarker = this.makeMarker(section+":");
var markerLength = 5 + sectionMarker.length - 3; // 5 = "<!-- ", 3 = "###"
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, markerLength);
for (var startIndex = 0; startIndex < tpl.length;) {
var foundIndex = tpl.indexOf(anyMarker, startIndex);
if (foundIndex==-1) {
startIndex = tpl.length;
} else {
var markerEnd = tpl.indexOf("###", foundIndex + markerLength);
var mode = tpl.substr(foundIndex + markerLength - 1, markerEnd - foundIndex - markerLength + 1); // Should be e.g. :EDIT
var endMarker = this.makeSectionMarker(this.makeMarker(section + mode), "END");
var endPos = tpl.indexOf(endMarker, markerEnd);
if (endPos==-1) {
startIndex = tpl.length;
} else {
startIndex = endPos + markerLength;
ret.push(mode);
} } }
return ret;
}
deleteSection(tpl, section) {
var sectionMarker = this.makeMarker(section);
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = tpl.indexOf(anyMarker);
var endPos = tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
return tpl;
}
var ret = (startPos>0 ? tpl.substr(0, startPos) : "");
if (endPos<tpl.length) ret+= tpl.substr(endPos);
return ret;
}
findAndDeleteUnneededModes(tpl, section, mode = "") {
var ret = tpl;
// Find and destroy unneeded mode siblings
var modes = this.findMarkerModes(tpl, section);
var chosenMode = mode;
while (!modes.includes(chosenMode) && this.modeFallback[chosenMode]!=null) {
chosenMode = this.modeFallback[chosenMode];
}
for (var i in modes) {
if (modes[i]!=chosenMode) {
ret = this.deleteSection(ret, section + modes[i]);
}
}
if (chosenMode!="") {
ret = this.deleteSection(ret, section);
}
return {"tpl": ret, "mode": chosenMode};
}
extractSection(tpl, section, mode = "") { //Extracts a section from a template
var sectionMarker = this.makeMarker(section + mode);
var startMarker = this.makeSectionMarker(sectionMarker, "START");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = tpl.indexOf(startMarker) + startMarker.length;
var endPos = tpl.lastIndexOf(endMarker);
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
return (this.modeFallback[mode]!=null ? this.extractSection(tpl, section, this.modeFallback[mode]) : "");
}
return tpl.substr(startPos,endPos-startPos);
}
replaceSection(tpl, section, value, mode = "") { //replaces a section in a template with a value
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var startMarker = this.makeSectionMarker(sectionMarker, "START");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = modeTpl.tpl.indexOf(startMarker);
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {return modeTpl.tpl;}
var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceCondition(tpl, section, bool, mode = "") { // selects one of two sections in a template
// Template looks like this:
// <!-- ###SECTION### IF -->
// True text
// <!-- ###SECTION### ELSE -->
// False text
// <!-- ###SECTION### END -->
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var ifMarker = this.makeSectionMarker(sectionMarker, "IF");
var elseMarker = this.makeSectionMarker(sectionMarker, "ELSE");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var startPos = modeTpl.tpl.indexOf(ifMarker);
var else_startPos = modeTpl.tpl.indexOf(elseMarker);
var else_endPos = else_startPos + elseMarker.length;
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if ((startPos==-1) || (else_startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
if (bool) {
return modeTpl.tpl;
} else {
return this.replaceSection(modeTpl.tpl, section, "", modeTpl.mode);
}
}
var value = "";
if (bool) {
// Show if-part
value = modeTpl.tpl.substr(startPos, else_startPos - startPos);
} else {
// Show else-part
value = modeTpl.tpl.substr(else_endPos, endPos - else_endPos - endMarker.length);
}
var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceSwitch(tpl, section, keys, mode = "") { // selects one or more of a variable number of cases from a section in a template
// Template looks like this:
// <!-- ###SECTION### SWITCH -->
// Default text
// <!-- ###SECTION### CASE_A -->
// Text A
// <!-- ###SECTION### CASE_B -->
// Text B
// <!-- ###SECTION### END -->
var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
var sectionMarker = this.makeMarker(section + modeTpl.mode);
var switchMarker = this.makeSectionMarker(sectionMarker, "SWITCH");
var endMarker = this.makeSectionMarker(sectionMarker, "END");
var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
var switchPos = modeTpl.tpl.indexOf(switchMarker);
var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
if (switchPos==-1 || endPos==-1) {return modeTpl.tpl;}
var value = "";
for (var i = 1; i<keys.length; i++) {
var keyMarker = this.makeSectionMarker(sectionMarker, keys[i]);
var keyPos = modeTpl.tpl.indexOf(keyMarker);
if (keyPos==-1) {
keyPos = switchPos + switchMarker.length; // Case `key` wasn't found, use default case
} else {
keyPos+= keyMarker.length;
}
// Find the start of the next marker.
var nextPos = modeTpl.tpl.indexOf(anyMarker, keyPos);
if (nextPos==-1) {
// Shouldn't happen. If it does, endMarker is before keyMarker
nextPos = endPos;
}
value+= modeTpl.tpl.substr(keyPos, nextPos - keyPos);
}
var ret = (switchPos>0 ? modeTpl.tpl.substr(0, switchPos) : "");
ret+= value;
if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
return ret;
}
replaceMarker(tpl, marker, value) { //replaces a marker in a template with a value
return tpl.replaceAll(this.makeMarker(marker), value);
}
/* replaceMarkerArray(tpl, array) { //replaces all markers in a template with values from an array (index defines marker)
for (var ind in array)
{
tpl = tpl.replaceAll(this.makeMarker(ind), array[ind]);
}
return tpl;
}*/
}