import { resolveComplexTemplate } from "../../utils/template/resolveComplexTemplate.js";
import { enhanceHTML } from "../../utils/template/enhanceHTML.js";
import DefaultPopupTemplate from './default.popup.md?raw';
import DefaultSidebarTemplate from './default.sidebar.md?raw';
export { PopupRenderer }
/**
* Static class for resolving popup templates depending on the type and
* rendering the content.
*
* @author rhess <robin.hess@awi.de>
*
* @memberof vef.map.popup
*/
class PopupRenderer {
static templates = {};
static urlCache = {};
static templateUrlPrefix = "";
static registerTemplate(name, template) {
PopupRenderer.templates[name] = template;
}
/**
* Builds and returns an array of features and HTML describing
* the features defined in content.
*
* Returns an object like
* ```
* {
* response: response,
* feature: feature,
* layer: layer,
* html: html
* }
* ```
*
* @param {object} content
* @param {object} response
* @param {object} layer
* @param {string} style "popup" or "sidebar"
*/
static async render(content, response, layer, style) {
const uId = layer.uniqueId;
// simple handling of unparsed responses (e.g. html)
if ((typeof content == "string") || (content instanceof HTMLElement)) {
return [{
response: response,
feature: content,
layer: layer,
html: content
}];
}
// handle invalid content
if (!layer) {
console.warn('Layer was not defined for the popup content');
return [];
} else if (!content || (typeof content != "object")) {
console.warn('Empty or error content for ', uId);
return [];
} else if (!["FeatureCollection", "Feature"].includes(content?.type)) {
console.warn('The popup content cannot be parsed as a GeoJson FeatureCollection');
return [];
} else if (content.ServiceExceptionReport) {
console.warn('Exception report for ', uId, content.ServiceExceptionReport);
return [];
}
if (content.type == "Feature") content = {
type: "FeatureCollection",
features: [content]
}
if (!("features" in content)) {
console.warn('"features" is missing in the GeoJson FeatureCollection');
return [];
}
// resolve and replace templates
let template = await PopupRenderer.getTemplate(layer, style);
return PopupRenderer.renderTemplate(content, response, layer, style, template);
}
/**
* fetch a file based template via url. Check if the url starts with this.templateUrlPrefix first.
*
* @param {string} url
* @returns {string} template
*/
static fetchTemplate(url) {
return new Promise((resolve, reject) => {
if (url in this.urlCache) {
resolve(this.urlCache[url]);
} else if (url.startsWith(this.templateUrlPrefix)) {
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = e => {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
PopupRenderer.urlCache[url] = xhttp.responseText;
resolve(xhttp.responseText);
} else {
reject(`could not load template: ${url}, code: ${xhttp.status}`)
}
}
};
xhttp.onerror = e => {
reject(e);
}
xhttp.open("GET", url, true);
xhttp.send();
} else {
reject(`Invalid url prefix: ${this.templateUrlPrefix}`);
}
});
}
static getDefaultTemplate(style, layer) {
console.log('Using default template for', layer.uniqueId);
return (style == "sidebar") ? DefaultSidebarTemplate : DefaultPopupTemplate;
}
/**
* Get the template for a layer without resolving it
*
* @param {Layer} layer
* @param {string} style popup or sidebar
* @returns {string} template
*/
static async getTemplate(layer, style) {
const resolveString = async (template) => {
let result = null;
if (template.startsWith('id::')) {
if (template.substr(3) in PopupRenderer.templates) {
console.log('Using named popup template', template, 'for', layer.uniqueId);
result = PopupRenderer.templates[template.substr(3)];
}
} else if (template.startsWith('https://') || template.startsWith('https://')) {
console.log('Using file based template', template, 'for', layer.uniqueId);
try {
result = await PopupRenderer.fetchTemplate(template);
}
catch (error) {
console.warn(error);
}
} else {
console.log('Using integrated template for', layer.uniqueId);
result = template;
}
return result;
}
const template = layer.metadataTemplate;
let result = null;
if (typeof template == "string") {
result = await resolveString(template);
} else if ((typeof template == "object") && template) {
if (!style || !(style in template)) style = "popup";
if ((style in template) && (typeof template[style] == "string")) {
result = await resolveString(template[style]);
}
}
return (result) ? result : this.getDefaultTemplate(style, layer);
}
static renderTemplate(featureCollection, response, layer, style, template) {
const responses = [];
// interpret strings as markdown and resolve template syntax
if (typeof template !== 'string') return responses;
for (let i = 0; i < featureCollection.features.length; ++i) {
const feature = featureCollection.features[i];
const data = Object.assign({ layer: layer, response: response }, feature)
try {
const html = resolveComplexTemplate(template, data, {
markdown: true,
postProcess: true
});
enhanceHTML(html, (style == "sidebar") ? {} : { groupedHeadlines: false })
responses.push({
response: response,
feature: feature,
layer: layer,
title: html.dataset.title || layer.title,
html: html
});
} catch (msg) {
if (msg != "discard_template") console.warn(msg);
}
}
return responses;
}
}