import { Shark } from "../Shark";
import Mustache from "mustache";
import $ from "jquery";
import SmartModel from "../SmartModel";
import { Utils } from "../Utils";
import parser from "html2hscript";
import h from "virtual-dom/h";
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
import createElement from 'virtual-dom/create-element';
/**
* Is the bridge between the Shark main class and VirtualDOM.
* @constructor
*/
class VirtualDOMRenderer {
constructor () {
if (typeof Mustache === "undefined") {
throw (new Error("Mustache is needed to use VirtualDOMRenderer!"));
}
this.rootNode = null;
this.tree = null;
}
/**
* Takes a Jaw, get the template from Shark, put the dataObject from the Jaw into the template and add the rendered data into the container
* passed in the options.
*
* @param {Jaw} jaw - The view to render.
* @param {object} options - The object with the options to setup the render.
* @param {string} options.container - The CSS selector to use to insert the rendered content. To place the content is used jQuery.html() method.
*/
render (jaw, options) {
return new Promise((resolve, reject) => {
console.time("VirtualDOMRenderer.render");
//Dave.ToDo: Qui va inserita tutta la validazione per essere certi che il renderer abbia tutto ciò che gli server.
Shark.trace("VirtualDOMRenderer.render: ", jaw, options, Shark.TRACE_INFO);
var defaultOptions = {
renderOnScreen: true
};
options = $.extend(defaultOptions, options);
if (!jaw.__templateParsed) {
//Dave.ToDo: Sarebbe interessante poter gestire più template per un singolo componente (tipo, il bottone che apre il modal e il modal) da poter richiamare tipo componente.template
var templateContent = jaw.templateContent;// || Shark.getTemplate(jaw.templateRoute);
//Dave.ToDo: Questa cosa andrebbe spostata nell'init, così da non doverlo fare ad ogni render
// Dave: nei renderer moderni i subTemplate non ci devono essere > andrà sostituito con una gestione dei "fragments"
// var pc = 0;
// var subTemplateList = templateContent.match(/{{>[\w\.]+\}\}/g);
// while (subTemplateList && pc < 100) {
// subTemplateList.forEach(function (item) {
// var componentName = item.replace("{{>", "").replace("}}", "");
// if (componentName) {
// //Dave: ho rimosso "Shark.getTemplate" ma qui non lo tolgo.. se qualcuno lo usa si deve rompere..
// templateContent = templateContent.replace(item, Shark.getTemplate(componentName));
// }
// else {
// Shark.trace("VirtualDOMRenderer.render > Missing SubTemplate: " + componentName, Shark.TRACE_ERROR);
// }
// });
// subTemplateList = templateContent.match(/{{>[\w\.]+\}\}/g);
// pc++;
// }
var componentList = templateContent.match(/{{=>[\w\.]+\}\}/g);
// var componentTemplates = {};
while (componentList) {
componentList.forEach(function (item) {
let processDefaultTemplate = true;
let componentName = item.replace("{{=>", "").replace("}}", "");
if (componentName.indexOf(".") > -1) {
componentName = componentName.substr(0, componentName.indexOf("."));
processDefaultTemplate = false;
}
let component = Shark.__components[componentName];
if (component) {
if (processDefaultTemplate) {
templateContent = templateContent.replace(item, component.templateContent/* || Shark.getTemplate(component.templateRoute)*/); //templateRoute will be Deprecated
}
if (component.__templates) {
for (let currTemplateName in component.__templates) {
templateContent = templateContent.replace(`{{=>${componentName}.${currTemplateName}}}`, component.__templates[currTemplateName]);
}
}
}
else {
Shark.trace("VirtualDOMRenderer.render > Missing component: " + componentName, Shark.TRACE_ERROR);
templateContent = templateContent.replace(item, item.replace("{{=>", "{{>"));
}
});
componentList = templateContent.match(/{{=>[\w\.]+\}\}/g);
}
//------------------------------------------------------------------------
let customHtmlComponentRegEx = new RegExp(/<shark-[\w]+.+\/*>/g);
let customHtmlComponentList = templateContent.match(customHtmlComponentRegEx);
while (customHtmlComponentList) {
customHtmlComponentList.forEach(function (item) {
let componentName = item;
let templateName = "";
if (componentName.indexOf("></shark-") > -1) {
componentName = componentName.substr(0, componentName.indexOf("></shark-"));
}
if (componentName.indexOf("/>") > -1) {
componentName = componentName.substr(0, componentName.indexOf("/>"));
}
if (componentName.indexOf(">") > -1) {
componentName = componentName.substr(0, componentName.indexOf(">"));
}
if (componentName.indexOf(" ") > -1) {
componentName = componentName.substr(0, componentName.indexOf(" "));
}
componentName = componentName.replace("<shark-", "");
if (componentName.indexOf("-") > -1) {
componentName = Utils.dashedToCamel(componentName);
}
let templateAttrStart = item.indexOf("template=\"");
if (templateAttrStart > -1) {
templateName = item.substring(templateAttrStart + 10, item.indexOf("\"", templateAttrStart + 11));
if (templateName === "default") {
templateName = "";
}
}
let component = Shark.__components[componentName];
if (component) {
if (templateName === "") {
templateContent = templateContent.replace(item, component.templateContent);
}
else {
if (component.__templates && component.__templates[templateName]) {
templateContent = templateContent.replace(item, component.__templates[templateName]);
}
}
}
else {
Shark.trace("VirtualDOMRenderer.render > Missing component: " + componentName + ", removing from template.", Shark.TRACE_ERROR);
templateContent = templateContent.replace(item, "");
}
});
//Dave.Warn: Se un componente non esiste, si incastra per sempre in questo giro...
customHtmlComponentList = templateContent.match(customHtmlComponentRegEx);
}
//------------------------------------------------------------------------
templateContent = templateContent.replace(/data-sh-model=\"(.\S+)\"/g, `$& data-shguid="{{$1.__shguid}}"`);
// templateContent = templateContent.replace(/data-sh-model=\"(.\S+)\"/g, `$& data-__shguid="{{__shguid}}"`);
jaw.__autoBoundListSection = [];
const mustacheDynamic = templateContent.match(/{{\[\>[\w\.]+\}\}/g);
// const mustacheDynamic = /{{\[\>[\w\.]+\}\}/g.exec(templateContent);
if (mustacheDynamic) {
mustacheDynamic.forEach(listSection => {
const propertyName = listSection.replace("{{[>", "").replace("}}", "");
const mustacheDynamicRE = new RegExp("{{\\[\\>" + propertyName + "\\}\\}([\\s\\S]*){{" + propertyName + "\\<\\]\\}\\}", "g");
const templates = mustacheDynamicRE.exec(templateContent);
let itemTemplate = templates[1];
const tagStart = itemTemplate.indexOf(">");
itemTemplate = itemTemplate.substr(0, tagStart) + ` data-shguid="{{__shguid}}"` + itemTemplate.substr(tagStart);
console.log({ listSection });
jaw.__autoBoundListSection.push({
propertyName,
template: itemTemplate
});
const openTagRE = new RegExp("{{\\[\\>(" + propertyName + ")\\}\\}", "g");
const closeTagRE = new RegExp("{{(" + propertyName + ")\\<\\]\\}\\}", "g");
templateContent = templateContent.replace(openTagRE, "<shark-list property=\"" + propertyName + "\">{{#$1}}");
templateContent = templateContent.replace(closeTagRE, "{{/$1}}</shark-list>");
});
console.log({ __autoBoundListSection: jaw.__autoBoundListSection });
}
// /{{\[\>taskList\}\}([\s\S]*){{taskList\<\]\}\}/g
//Occhio alle {{{ }}}
//First attempt to manage autorender of bound properties
console.time("autoBind");
Shark.trace({
"renderField": templateContent.match(/{{[\w\.]+\}\}/g),
"renderListOrPresence": templateContent.match(/{{#[\w\.]+\}\}/g),
"renderAbsence": templateContent.match(/{{\^[\w\.]+\}\}/g),
}, Shark.TRACE_LOG);
Shark.trace({
"renderField": [...new Set(templateContent.match(/{{[\w\.]+\}\}/g))].map(item => item.replace("{{", "").replace("}}", "")),
"renderListOrPresence": [...new Set(templateContent.match(/{{#[\w\.]+\}\}/g))].map(item => item.replace("{{#", "").replace("}}", "")),
"renderAbsence": [...new Set(templateContent.match(/{{\^[\w\.]+\}\}/g))].map(item => item.replace("{{^", "").replace("}}", "")),
}, Shark.TRACE_LOG);
const renderField = [...[...new Set(templateContent.match(/{{[\w\.]+\}\}/g))].map(item => item.replace("{{", "").replace("}}", "")),
...[...new Set(templateContent.match(/{{#[\w\.]+\}\}/g))].map(item => item.replace("{{#", "").replace("}}", "")),
...[...new Set(templateContent.match(/{{\^[\w\.]+\}\}/g))].map(item => item.replace("{{^", "").replace("}}", ""))];
Shark.__autoBoundProperties = [];
for (const value in Shark.__store) {
if (renderField.indexOf(value) > -1) {
Shark.__autoBoundProperties.push(value);
jaw.__autoBoundProperties.push(value);
}
}
Shark.trace({ renderField, SharkAutoBoundProperties: Shark.__autoBoundProperties, JawAutoBoundProperties: jaw.__autoBoundProperties });
console.timeEnd("autoBind");
for (const value in Shark.__store) {
if (Shark.__store[value] instanceof SmartModel) {
Shark.__store[value].__boundProperties = [];
}
}
// for (const value in Shark.appData) {
// if (Shark.appData[value] instanceof SmartModel) {
// Shark.appData[value].__boundProperties = [];
// }
// }
const dynamicFields = templateContent.match(/{{[\w\.]+\}\}/g);
// var dynamicFields = templateContent.match(/[{[\w\.]+\]\}/g);
if (dynamicFields && dynamicFields.length > 0) {
dynamicFields.forEach(item => {
// const propertyData = item.replace("{[", "").replace("]}", "").split(".");
const propertyData = item.replace("{{", "").replace("}}", "").split(".");
const model = Shark.get(propertyData[0]);
if (model && model instanceof SmartModel) {
if (model.__boundProperties.indexOf(propertyData[1]) === -1) {
model.__boundProperties.push(propertyData[1]);
Shark.trace("dynamicFields", model, "log");
}
}
// templateContent = templateContent.replace(item, "");
});
}
Shark.__dynamicSections = {};
const dynamicSections = templateContent.match(/\<shark-section(.*?)\>/g);
// const dynamicSections = templateContent.match(/{{@[\w\.]+\}\}/g);
if (dynamicSections && dynamicSections.length > 0) {
dynamicSections.forEach(item => {
//<shark-section model="commentList">
//</shark-section>
const openTag = item; //.replace("@", "#");
const closeTag = "</shark-section>"; //item.replace("@", "@/");
const dynamicSection = {
closeTag,
openTag,
rGuid: Shark.__generateGUID("rid_"),
storeReference: /model=\"(.*?)\"/g.exec(item)[1] // item.replace("{{@", "").replace("}}", "")
};
var templateRE = new RegExp(openTag + "(.*?)" + closeTag.replace("/", "\/"), "gs");
dynamicSection.templateContent = templateRE.exec(templateContent)[1];
// dynamicSection.templateContent = openTag + templateRE.exec(templateContent)[1] + closeTag;
if (window.customElements) {
templateContent = templateContent.replace(item, `<shark-section data-sh-rguid='${dynamicSection.rGuid}'>${openTag}`).replace(closeTag, `${closeTag}</shark-section>`);
}
else {
templateContent = templateContent.replace(item, `<span data-sh-rguid='${dynamicSection.rGuid}'>${openTag}`).replace(closeTag, `${closeTag}</span>`);
}
if (Shark.__dynamicSections[dynamicSection.storeReference]) {
Shark.__dynamicSections[dynamicSection.storeReference].push(dynamicSection);
}
else {
Shark.__dynamicSections[dynamicSection.storeReference] = [dynamicSection];
}
});
Shark.trace("dynamicSections", Shark.__dynamicSections, "log");
}
templateContent = templateContent.replace(/\[\[([\w\.]+)\]\]/g, `{{{__renderer.$1}}}`);
// templateContent = templateContent.replace(/\r\n/g, "");
templateContent = templateContent.replace(/\t/g, "");
templateContent = templateContent.replace(/<br[\s\S\ ]*?(?=>)/g, "$&/");
templateContent = templateContent.replace(/<hr[\s\S\ ]*?(?=>)/g, "$&/");
templateContent = templateContent.replace(/<img [\s\S\ ]*?(?=>)/g, "$&/");
templateContent = templateContent.replace(/<input [\s\S\ ]*?(?=>)/g, "$&/");
jaw.__parsedTemplate = templateContent;
jaw.__templateParsed = true;
}
const componentsStore = {};
const processedComponentList = [];
jaw.__activeComponents.forEach(component => {
if (processedComponentList.indexOf(component.id) === -1) {
Object.assign(componentsStore, component.__store);
}
//Ovviamente questa cosa va estratta per essere recursiva
component.__activeComponents.forEach(subComponent => {
if (processedComponentList.indexOf(subComponent.id) === -1) {
Object.assign(componentsStore, subComponent.__store);
}
});
});
// jaw.__activeComponents.forEach(function (item) {
// Object.assign(componentsStore, item.__store);
// });
const childTemplates = $.extend({}, Shark.__templateMap, jaw.__childTemplates);
const renderStore = Object.assign({}, Shark.__store, componentsStore, jaw.__store);
// console.log({ renderStore, Shark__store: Shark.__store, componentsStore, jaw__store: jaw.__store });
let output = Mustache.render(jaw.__parsedTemplate, renderStore, childTemplates);
if (options.renderOnScreen) {
const that = this;
output = "<span>" + output + "</span>";
if (!that.tree) {
//DAve: QUesto è asincrono, dovrei modificarlo per restituire una promise a Shark.render()
console.time("VirtualDOMRenderer.patch");
parser(output.trim(), function (err, hscript) {
that.tree = new Function('h','return '+ hscript)(h);
that.rootNode = createElement(that.tree);
$(options.container).append(that.rootNode);
console.timeEnd("VirtualDOMRenderer.render");
console.timeEnd("VirtualDOMRenderer.patch");
resolve();
});
}
else {
//DAve: QUesto è asincrono, dovrei modificarlo per restituire una promise a Shark.render()
console.time("VirtualDOMRenderer.patch");
parser(output.trim(), function (err, hscript) {
var newTree = new Function('h','return '+ hscript)(h);
var patches = diff(that.tree, newTree);
that.rootNode = patch(that.rootNode, patches);
that.tree = newTree;
console.timeEnd("VirtualDOMRenderer.render");
console.timeEnd("VirtualDOMRenderer.patch");
resolve();
});
}
}
else {
resolve(output);
// return output;
}
});
}
}
export default VirtualDOMRenderer;