begin of new templater
94a3fc3fe2b57acea84c6e6ff294cf0adc738051
3 files changed
src/NetproxySolution.Web/wwwroot/Template.htmlsrc/NetproxySolution.Web/wwwroot/scripts/netproxy.jssrc/NetproxySolution.Web/wwwroot/template.js
diff --git a/src/NetproxySolution.Web/wwwroot/Template.html b/src/NetproxySolution.Web/wwwroot/Template.html
new file mode 100644
index 0000000..35b6fdc
--- /dev/null
+++ b/src/NetproxySolution.Web/wwwroot/Template.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>TemplateHtml Demo</title>
+ <style>
+ table
+ {
+ border-collapse: collapse;
+ margin: 10px 0;
+ }
+
+ th, td
+ {
+ border: 1px solid #ccc;
+ padding: 8px;
+ text-align: left;
+ }
+
+ .nested-table
+ {
+ margin: 0;
+ }
+
+ .nested-table td
+ {
+ border: 1px solid #ddd;
+ padding: 4px;
+ }
+ </style>
+</head>
+<body>
+ <!-- Template 1: Simple output -->
+ <script id="template1" type="text/template">
+ Hello, <%=user.name%>!
+ </script>
+ <!-- Template 2: Nested properties -->
+ <script id="template2" type="text/template">
+ <p>User: <%=user.profile.firstName%> <%=user.profile.lastName%></p>
+ </script>
+ <!-- Template 3: Control structure (loop) -->
+ <script id="template3" type="text/template">
+ <ul>
+ <% for (let i = 0; i < items.length; i++) { %>
+ <li><%=items[i]%></li>
+ <% } %>
+ </ul>
+ </script>
+ <!-- Template 4: Error handling -->
+ <script id="template4" type="text/template">
+ <div><%=user.missingProperty?.subProperty%></div>
+ </script>
+ <!-- Template 5: Combined code and output -->
+ <script id="template5" type="text/template">
+ <div>
+ <% let greeting = "Welcome"; %>
+ <%=greeting%>, <%=user.name%>!
+ </div>
+ </script>
+ <!-- Template 6: Multiple expressions -->
+ <script id="template6" type="text/template">
+ <p>Hello <%=user.firstName%> <%=user.lastName%>, your score is <%=user.score%>!</p>
+ </script>
+ <!-- Template 7: Complex table with nested tables -->
+ <script id="template7" type="text/template">
+ <table>
+ <tr>
+ <th>Team</th>
+ <th>Members</th>
+ <th>Projects</th>
+ <th>Total Score</th>
+ </tr>
+ <% for (let i = 0; i < teams.length; i++) { %>
+ <% let team = teams[i]; %>
+ <tr>
+ <td><%=team.name%></td>
+ <td>
+ <table class="nested-table">
+ <tr>
+ <th>Name</th>
+ <th>Role</th>
+ </tr>
+ <% for (let j = 0; j < team.members.length; j++) { %>
+ <tr>
+ <td><%=team.members[j].name%></td>
+ <td><%=team.members[j].role%></td>
+ </tr>
+ <% } %>
+ </table>
+ </td>
+ <td>
+ <table class="nested-table">
+ <tr>
+ <th>Project</th>
+ <th>Status</th>
+ </tr>
+ <% for (let k = 0; k < team.projects.length; k++) { %>
+ <tr>
+ <td><%=team.projects[k].name%></td>
+ <td><%=team.projects[k].status%></td>
+ </tr>
+ <% } %>
+ </table>
+ </td>
+ <td>
+ <% let totalScore = team.members.reduce((sum, member) => sum + (member.score || 0), 0); %>
+ <%=totalScore%>
+ </td>
+ </tr>
+ <% } %>
+ </table>
+ </script>
+ <!-- Output container -->
+ <div id="output" style="margin-top: 20px;"></div>
+ <script src="template.js" defer></script>
+ <script>
+ function runTests()
+ {
+ const output = document.getElementById("output");
+ // Example 1: Simple output
+ const data1 = { user: { name: "John" } };
+ output.Template("template1", data1, true);
+ // Example 2: Nested properties
+ const data2 = { user: { profile: { firstName: "Jane", lastName: "Doe" } } };
+ output.Template("template2", data2, true);
+ // Example 3: Control structure (loop)
+ const data3 = { items: ["Apple", "Banana", "Orange"] };
+ output.Template("template3", data3, true);
+ // Example 4: Error handling
+ const data4 = { user: {} };
+ output.Template("template4", data4, true);
+ // Example 5: Combined code and output
+ const data5 = { user: { name: "Alice" } };
+ output.Template("template5", data5, true);
+ // Example 6: Multiple expressions
+ const data6 = { user: { firstName: "Bob", lastName: "Smith", score: 95 } };
+ output.Template("template6", data6, true);
+ // Example 7: Complex table with nested tables
+ const data7 = {
+ teams: [
+ {
+ name: "Alpha",
+ members: [
+ { name: "Alice", role: "Developer", score: 90 },
+ { name: "Bob", role: "Designer", score: 85 }
+ ],
+ projects: [
+ { name: "Website Redesign", status: "In Progress" },
+ { name: "Mobile App", status: "Completed" }
+ ]
+ },
+ {
+ name: "Beta",
+ members: [
+ { name: "Charlie", role: "Manager", score: 95 },
+ { name: "Diana", role: "Tester", score: 80 }
+ ],
+ projects: [
+ { name: "API Integration", status: "Planned" }
+ ]
+ }
+ ]
+ };
+ output.Template("template7", data7, true);
+ }
+ document.addEventListener("DOMContentLoaded", runTests);
+ </script>
+</body>
+</html>
diff --git a/src/NetproxySolution.Web/wwwroot/scripts/netproxy.js b/src/NetproxySolution.Web/wwwroot/scripts/netproxy.js
index 319609e..e0f4420 100644
--- a/src/NetproxySolution.Web/wwwroot/scripts/netproxy.js
+++ b/src/NetproxySolution.Web/wwwroot/scripts/netproxy.js
@@ -3,7 +3,7 @@
* @name pure-dom netproxy and template api
*
* @author Alphons van der Heijden <alphons@heijden.com>
- * @version 3.0.2 (last revision Nov, 2024)
+ * @version 3.0.3 (last revision 30 aug, 2025)
* @copyright (c) 2019-2024 Alphons van der Heijden
* @alias netproxy, netproxyasync, Element.Template, TemplateHtml
*
@@ -18,9 +18,13 @@
function escapeHtml(unsafe)
{
if (Array.isArray(unsafe))
+ {
return unsafe.map(item => escapeHtml(item)).join(', ');
+ }
if (unsafe === null || unsafe === undefined)
+ {
return '';
+ }
return String(unsafe).replace(/[&<>"']/g, function (match)
{
return {
@@ -33,34 +37,52 @@
});
}
- // netproxy functionaliteit
- window.netproxy = function (url, data, onsuccess, onerror, onprogress)
+ // Helper functie voor spinner logica
+ function manageSpinner(spinner, show, timeoutSpinner)
+ {
+ if (spinner)
+ {
+ spinner.style.display = show ? 'block' : 'none';
+ }
+ if (timeoutSpinner && !show)
+ {
+ clearTimeout(timeoutSpinner);
+ }
+ }
+
+ // Netproxy functionaliteit
+ window.netproxy = function (url, data, onsuccess, onerror, onprogress, timeout = 30000)
{
const spinner = document.getElementById("netproxyspinner");
if (typeof remote !== 'undefined')
+ {
url = remote + url;
- if(spinner)
- var timeoutSpinner = setTimeout(() => spinner.style.display = 'block', 1000);
+ }
+
+ const timeoutSpinner = spinner ? setTimeout(() => manageSpinner(spinner, true), 1000) : null;
const xhr = new XMLHttpRequest();
xhr.open(data ? 'POST' : 'GET', url, true);
xhr.withCredentials = url.indexOf(window.location.host) < 0 && url[0] !== '/';
+
if (!(data instanceof FormData))
{
- xhr.timeout = 30000;
+ xhr.timeout = timeout;
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
}
+
xhr.onloadend = function ()
{
- if (timeoutSpinner)
- clearTimeout(timeoutSpinner);
- if (spinner)
- spinner.style.display = 'none';
+ manageSpinner(spinner, false, timeoutSpinner);
+
if (xhr.status === 204)
{
if (typeof onsuccess === 'function')
+ {
onsuccess.call(null, null);
+ }
return;
}
+
if (xhr.status >= 200 && xhr.status < 300)
{
const contentDisposition = xhr.getResponseHeader('Content-Disposition');
@@ -86,12 +108,15 @@
}
catch (e)
{
+ // Blijf bij tekst als JSON-parsen faalt
}
try
{
if (typeof onsuccess === 'function')
+ {
onsuccess.call(response, response);
+ }
return;
}
catch (error)
@@ -99,11 +124,11 @@
if (typeof onerror === 'function')
{
onerror.call(xhr, error);
- return;
}
- throw error;
+ return;
}
}
+
if (xhr.status >= 400)
{
const error = new Error(`Http:${xhr.status}`);
@@ -115,10 +140,11 @@
}
throw error;
}
+
if (xhr.status === 0)
{
const error = xhr.timedout
- ? new Error(`Timeout ${xhr.timeout}ms for ${url}`)
+ ? new Error(`Timeout ${timeout}ms for ${url}`)
: new Error(`Network error or request canceled for ${url}`);
error.path = url;
if (typeof onerror === 'function')
@@ -129,21 +155,21 @@
throw error;
}
};
- if (onprogress)
+
+ if (typeof onprogress === 'function')
{
xhr.upload.onprogress = onprogress;
xhr.onprogress = onprogress;
}
- xhr.send(data instanceof FormData
- ? data
- : JSON.stringify(data));
+
+ xhr.send(data instanceof FormData ? data : JSON.stringify(data));
};
- window.netproxyasync = function (url, data, onprogress)
+ window.netproxyasync = function (url, data, onprogress, timeout = 30000)
{
return new Promise((resolve, reject) =>
{
- netproxy(url, data, resolve, reject, onprogress);
+ window.netproxy(url, data, resolve, reject, onprogress, timeout);
});
};
diff --git a/src/NetproxySolution.Web/wwwroot/template.js b/src/NetproxySolution.Web/wwwroot/template.js
new file mode 100644
index 0000000..f20a6ef
--- /dev/null
+++ b/src/NetproxySolution.Web/wwwroot/template.js
@@ -0,0 +1,121 @@
+// Templater 3.0 AAB van der Heijden 2025-08-30
+function XSS(unsafe)
+{
+ if (Array.isArray(unsafe))
+ {
+ return unsafe.map(item => XSS(item)).join(', ');
+ }
+ if (unsafe === null || unsafe === undefined)
+ {
+ return '';
+ }
+ return String(unsafe).replace(/[&<>"']/g, match =>
+ ({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[match]));
+}
+
+// Template functionality
+Element.prototype.Template = function (template, data, append)
+{
+ var strHtml = window.TemplateHtml(template, data);
+ if (append)
+ {
+ var temp = document.createElement("span");
+ temp.innerHTML = strHtml;
+ this.insertAdjacentElement('beforeend', temp);
+ }
+ else
+ {
+ this.innerHTML = strHtml;
+ }
+};
+
+function TemplateHtml(template, data)
+{
+ try
+ {
+ var element = typeof template === "string" ? document.getElementById(template) : template;
+ if (!element)
+ throw new Error("Template not found.");
+ if (!element.jscache)
+ {
+ var html = element.textContent.replace(/\s+/g, ' ').trim();
+ var jsParts = ["let _='';"];
+ var concatParts = [];
+ var index = 0;
+ while (index < html.length)
+ {
+ var start = html.indexOf('<%', index);
+ if (start < 0)
+ {
+ concatParts.push("'" + html.slice(index).replace(/'/g, "\\'") + "'");
+ if (concatParts.length > 0)
+ {
+ jsParts.push("_+=" + concatParts.join('+') + ";");
+ concatParts = []; // Clear concatParts after pushing final segment
+ }
+ break;
+ }
+ if (start > index)
+ {
+ concatParts.push("'" + html.slice(index, start).replace(/'/g, "\\'") + "'");
+ }
+ index = start + 2;
+ var is_ = html[index] === '=';
+ if (is_)
+ index++;
+ var end = html.indexOf('%>', index);
+ if (end < 0)
+ throw new Error("Unclosed template tag.");
+ var code = html.slice(index, end).trim();
+ if (is_)
+ {
+ concatParts.push("XSS(" + code + ")");
+ }
+ else
+ {
+ if (concatParts.length > 0)
+ {
+ jsParts.push("_+=" + concatParts.join('+') + ";");
+ concatParts = [];
+ }
+ jsParts.push(code + ";");
+ }
+ index = end + 2;
+ }
+ if (concatParts.length > 0)
+ {
+ jsParts.push("_+=" + concatParts.join('+') + ";");
+ concatParts = []; // Clear concatParts after final push
+ }
+ jsParts.push('return _;');
+ var js = jsParts.join('');
+ jsParts = []; // Clear jsParts after creating js string
+ // Wrap script in a function to allow return statement
+ element.jscache = function (data)
+ {
+ var context = { ...data, XSS };
+ try
+ {
+ return (function (ctx) { with (ctx) { return eval('(function() {' + js + '})()'); } })(context);
+ }
+ catch (err)
+ {
+ console.error(`Template execution error: ${err.message}`);
+ return '';
+ }
+ };
+ }
+ return element.jscache(data);
+ }
+ catch (err)
+ {
+ console.error("Template error:", err.message);
+ return "";
+ }
+}