Introduction
I'm currently creating a templatebuilder where users can build a template for an app. The user can drag and drop multiple blocks, such as text blocks and 'custom code' blocks. The template will be parsed within an app. Right now, a template could look like this:
<section>
<div class="row">
<div class="col-sm-12">
<section data-type="code">
<#code></#code>
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12" data-type="container-content">
<section data-type="text">
<u>Lorem</u> ipsum
</section>
</div>
</div>
</section>
So, this template contains two elements (see the data-type
attribute): one part is custom written code. Here, the user wrote custom code, including Apache Freemarker code. The second part is custom written text.
Situation
The code above will be used in two different ways.
- Exactly this code will be used inside an app that's using the template (so that's why they should be able to write Freemarker code, because this will be parsed).
- On my website, the user should be able to edit this template. Because the code is stored in the database as written above, there is a problem:
Problem
When I directly render the template in the web-interface, the text
part will render correctly with the <u></u>
tags, but the code
part will be rendered as html as well which might cause weird behaviour (such as the freemarker notation </#list>
being auto-converted to <!--#list-->
).
But, if I render the full template as text only, the text
part with the <u></u>
tags will not be rendered as well.
Expected outcome
I want to read the template variable with JavaScript / jQuery and then parse each data-type
with text
as html, and with code
as text.
How can I loop through the template and do this?
Introduction
I'm currently creating a templatebuilder where users can build a template for an app. The user can drag and drop multiple blocks, such as text blocks and 'custom code' blocks. The template will be parsed within an app. Right now, a template could look like this:
<section>
<div class="row">
<div class="col-sm-12">
<section data-type="code">
<#code></#code>
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12" data-type="container-content">
<section data-type="text">
<u>Lorem</u> ipsum
</section>
</div>
</div>
</section>
So, this template contains two elements (see the data-type
attribute): one part is custom written code. Here, the user wrote custom code, including Apache Freemarker code. The second part is custom written text.
Situation
The code above will be used in two different ways.
- Exactly this code will be used inside an app that's using the template (so that's why they should be able to write Freemarker code, because this will be parsed).
- On my website, the user should be able to edit this template. Because the code is stored in the database as written above, there is a problem:
Problem
When I directly render the template in the web-interface, the text
part will render correctly with the <u></u>
tags, but the code
part will be rendered as html as well which might cause weird behaviour (such as the freemarker notation </#list>
being auto-converted to <!--#list-->
).
But, if I render the full template as text only, the text
part with the <u></u>
tags will not be rendered as well.
Expected outcome
I want to read the template variable with JavaScript / jQuery and then parse each data-type
with text
as html, and with code
as text.
How can I loop through the template and do this?
Share Improve this question edited Dec 26, 2017 at 23:37 Jordy asked Dec 20, 2017 at 15:43 JordyJordy 4,80912 gold badges51 silver badges82 bronze badges 15 | Show 10 more comments5 Answers
Reset to default 8 +50There is an alternative syntax that uses square brackets instead of angle brackets.
Check if it solves your tag identifying problem without messing any other feature.
https://freemarker.apache.org/docs/dgui_misc_alternativesyntax.html
EDIT 1
To show the source code inside the <#code> tags when the HTML is parsed, you could escape it in your database (escape html special chars like <, > and & to < > and &). So, when it is rendered, no html tags will be created in the code content and the document won't be messed up.
Then, you can render all the content of the database directly as HTML: text will keep markup and code will be text.
To do that modification, you can use regular expressions to find what is enclosed by <#code> tags and replace with the HTML-escaped equivalent. The exact way to do it depends on the language you will be using for the job, as there are some differences in RegExes and in the available escape funcions.
EDIT 2
If you are loading the content using AJAX, you have the chance of applying the replace in javascript, AFTER the content was obtained from the server, keeping your database as it is.
Recap of the problem
For parsing HTML in javascript, you generally use a DOMParser object (supported by IE10+).
Like you said, parsing fails inside the data-type="code"
section, because it does not know how to handle the </#...>
tags...
const templ = `<section><div class="row"><div class="col-sm-12"><section data-type="code"><#code></#code></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>`;
const parser = new DOMParser();
const doc = parser.parseFromString(templ, "text/html");
console.log(
"Wrongly parsed </#code> tag:\n",
doc.querySelector("[data-type='code']").innerHTML
);
Finding a way around
Now, it might sound like a good idea to try and do a quick regex find-and-replace on the characters that need to be escaped, but I wouldn't recommend it...
As far as I know, there's no way to "break in" to the parsing process or pass a strategy for certain types of elements...
I'd say this leaves you with two options. Either:
- Don't use the unparsable syntax inside the code section, as suggested by user Eduardo Poço in their answer
or, (my prefered direction), try to
- Modify the template itself to stop parsing the contents of the code sections all together
Using a modified template
There's a tag for "script" like content in HTML! It's, unsurprisingly, the <script>
tag. Let's inject it in our code
section:
<section data-type="code">
<script type="text">
<#code></#code>
</script>
</section>
The DOMParser
won't touch this tag, leaving it exactly as is:
const templ = '<section><div class="row"><div class="col-sm-12"><section data-type="code"><script type="text"><#code></#code></' + 'script></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>';
const parser = new DOMParser();
const doc = parser.parseFromString(templ, "text/html");
console.log(
"Now, there's a <script> tag:\n",
doc.querySelector("[data-type='code']").innerHTML
);
Note that I had to join the template string from two parts to make sure stackoverflow's snippet doesn't break. Are they experiencing a similar issue? :-o
Now, all we have to do is use the general DOM methods, including innerText
(not innerHTML
) to get the script's inner content back in to the visible part of the DOM:
var templ = '<section><div class="row"><div class="col-sm-12"><section data-type="code"><script type="text"><#code></#code></' + 'script></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>`;'
var parser = new DOMParser();
var doc = parser.parseFromString(templ, "text/html");
Array
.from(doc.querySelectorAll(
"[data-type='code'] > script")
)
.forEach(script => {
const codeTag = document.createElement("code");
codeTag.innerText = script.innerHTML;
script.replaceWith(codeTag);
});
document.getElementById("wrapper").appendChild(doc.body.firstChild);
code { background: #efefef; }
<div id="wrapper"></div>
You could use charset codes, so it does not execute before outputting. HTML charset reference
They can edit it, since it appears normal and send it back to you or server. Make sure you include your charset reference in the head.
<meta charset="UTF-8"> // HTML5
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"> // HTML4
<!-- @CHARSET / No execute -->
<section>
<div class="row">
<div class="col-sm-12">
<section data-type="code">
<#code> <!-- Run this --> </#code>
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12" data-type="container-content">
<section data-type="text">
<u> <!-- Don't run --> </u>
</section>
</div>
</div>
</section>
If i didn't misunderstand it, you can use <plaintext>
tag for rendering block as text on the page.
<plaintext>
<#code></#code>
</plaintext>
What about something like this?
// https://stackoverflow.com/questions/7477/autosizing-textarea-using-prototype
function FitToContent(id, maxHeight)
{
var text = id && id.style ? id : document.getElementById(id);
if (!text)
return;
/* Accounts for rows being deleted, pixel value may need adjusting */
if (text.clientHeight == text.scrollHeight) {
text.style.height = "30px";
}
var adjustedHeight = text.clientHeight;
if (!maxHeight || maxHeight > adjustedHeight)
{
adjustedHeight = Math.max(text.scrollHeight, adjustedHeight);
if (maxHeight)
adjustedHeight = Math.min(maxHeight, adjustedHeight);
if (adjustedHeight > text.clientHeight)
text.style.height = adjustedHeight + "px";
}
}
$('textarea').each(function(i,v){
FitToContent($(v)[0], document.documentElement.clientHeight)
});
textarea {
background: transparent;
border: 0;
font-family: 'Times New Roman';
font-size: 1em;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
<div class="row">
<div class="col-sm-12">
<h4>First code block</h4>
<section data-type="code">
<textarea class='code'><#code>
<h2>FreeMarker Spring MVC Hello World</h2>
<table class="datatable">
<tr>
<th>Make</th><th>Model</th>
</tr>
<#list model["carList"] as car>
<tr>
<td>${car.make}</td>
<td>${car.model}</td>
</tr>
</#list>
</table>
</#code></textarea>
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12" data-type="container-content">
<h4>Regular HTML section</h4>
<section data-type="text">
<u>Lorem</u> ipsum
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h4>Second code block</h4>
<section data-type="code">
<textarea class='code'><#code>
<table class="datatable">
<tr>
<th>Name</th><th>Email</th>
</tr>
<#list model["personList"] as person>
<tr>
<td>${person.name}</td>
<td>${person.email}</td>
</tr>
</#list>
</table>
</#code></textarea>
</section>
</div>
</div>
</section>
$('[data-type="code"]').each(function () {$(this).text($(this).html())});
do the trick? – Philipp Maurer Commented Dec 20, 2017 at 16:21$('[data-type="code"]')
only works on templates that are already parsed as html. And when I do that, the code part will be parsed and that's what I want to prevent. – Jordy Commented Dec 20, 2017 at 16:29