Problem
I have a collection of images with linked captions on a page. I want them each to have identical HTML.
Typically, i copy and paste the HTML over and over for each item. The problem is, if i want to tweak the HTML, i have to do it for all of them. It's time-consuming, and there's risk of mistakes.
Quick and Dirty Templating
I'd like to write just one copy of the HTML, list the content items as plain text, and on page-render the HTML would get automatically repeated for each content-item.
HTML
<p><img src=IMAGE-URL>
<br>
<a target='_blank' href=LINK-URL>CAPTION</a></p>
Content List
IMAGE-URL, LINK-URL, CAPTION
/data/khang.jpg, https://khangssite.com, Khang Le
/data/sam.jpg, https://samssite.com, Sam Smith
/data/joy.jpg, https://joyssite.com, Joy Jones
/data/sue.jpg, https://suessite.com, Sue Sneed
/data/dog.jpg, https://dogssite.com, Brown Dog
/data/cat.jpg, https://catssite.com, Black Cat
Single Item
Ideally, i could put the plain-text content for a single item anywhere on a page, with some kind of identifier to indicate which HTML template to use (similar to classes with CSS).
TEMPLATE=MyTemplate1, IMAGE-URL=khang.jpg, LINK-URL=https://khangssite.com, CAPTION=Khang Le
Implementation
Templating systems are widely used, like Django and Smarty on the server side, and Mustache on the client side. This question seeks a simple, single-file template solution, without using external libs.
I want to achieve this without a framework, library, etc. I'd like to put the HTML and content-list in the same .html file.
Definitely no database. It should be quick and simple to set it up within a page, without installing or configuring additional services.
Ideally, i'd like to do this without javascript, but that's not a strict requirement. If there's javascript, it should be ignorant of the fieldnames. Ideally, very short and simple. No jquery please.
you mean Template literals (Template strings) ?
const arrData =
[ { img: '/data/khang.jpg', link: 'https://khangssite.com', txt: 'Khang Le' }
, { img: '/data/sam.jpg', link: 'https://samssite.com', txt: 'Sam Smith' }
, { img: '/data/joy.jpg', link: 'https://joyssite.com', txt: 'Joy Jones' }
, { img: '/data/sue.jpg', link: 'https://suessite.com', txt: 'Sue Sneed' }
, { img: '/data/dog.jpg', link: 'https://dogssite.com', txt: 'Brown Dog' }
, { img: '/data/cat.jpg', link: 'https://catssite.com', txt: 'Black Cat' }
]
const myObj = document.querySelector('#my-div')
arrData.forEach(({ img, link, txt }) =>
{
myObj.innerHTML += `
<p>
<img src="${img}">
<br>
<a target='_blank' href="${link}">${txt}</a>
</p>`
});
<div id="my-div"></div>
This answer is a complete solution. It's exciting to edit the HTML template in codepen and watch the layout of each copy change in real time -- similar to the experience of editing a CSS class and watching the live changes.
Here's the code, followed by explanation.
HTML
<span id="template-container"></span>
<div hidden id="template-data">
IMG,, LINK,, CAPTION
https://www.referenseo.com/wp-content/uploads/2019/03/image-attractive.jpg,, khangssite.com,, Khang Le
https://i.redd.it/jeuusd992wd41.jpg,, suessite.com,, Sue Sneed
https://picsum.photos/536/354,, catssite.com,, Black Cat
</div>
<template id="art-template">
<span class="art-item">
<p>
<a href="${LINK}" target="_blank">
<img src="${IMG}" alt="" />
<br>
${CAPTION}
</a>
</p>
</span>
</template>
Javascript
window.onload = function LoadTemplate() {
// get template data.
let sRawData = document.querySelector("#template-data").innerHTML.trim();
// load header and data into arrays
const headersEnd = sRawData.indexOf("\n");
const headers = sRawData.slice(0, headersEnd).split(",,");
const aRows = sRawData.slice(headersEnd).trim().split("\n");
const data = aRows.map((element) => {
return element.split(",,");
});
// grab template and container
const templateHtml = document.querySelector("template").innerHTML;
const container = document.querySelector("#template-container");
// make html for each record
data.forEach((row) => {
let workingCopy = templateHtml;
// load current record into template
headers.forEach((header, column) => {
let value = row[column].trim();
let placeholder = `\$\{${header.trim()}\}`;
workingCopy = workingCopy.replaceAll(placeholder, value);
});
// append template to page, and loop to next record
container.innerHTML += workingCopy;
});
};
New version on github:
https://github.com/johnaweiss/HTML-Micro-Templating
Requirement
As specified in the question, this solution is intended to optimize the coding experience on the HTML side. That's the whole point of any web templating. Therefore, the JS has to work a little harder to make life easier for the HTML programmer.
The question seeks a reusable solution. Therefore, JS should be ignorant of the template, fields, and data-list. So unlike #MisterJojo's answer, the template and all data are in my HTML, not javascript. The JS code is generic.
Design
My solution is based on the <template> tag, which is intended for precisely this usage. It has various advantages, like the template isn't displayed, processed, or validated by the browser, so it has less impact on performance. Programmer doesn't have to write an explicit display:none style.
https://news.ycombinator.com/item?id=33089975
However, <template> tags are normally only intended for loading content into the layout. That's inadequate. This tool allows template variables anywhere in the HTML, including inside the tags (eg attributes like <img src).
HTML
My HTML has three blocks:
template: The HTML coder develops their desired display-structure of the output, in real HTML (not plain text). Uses <template>
data: The list of records each of which should be rendered using the same template. Uses <span> with a HIDDEN attribute.
container: The place to display all the output blocks. Uses <span>.
Template
My sample template includes 3 placeholders for data:
${LINK}
${IMG}
${CAPTION}
But of course you can use any placeholders, any number of them. I use string-literal delimiting-style (although i'm not actually using them as string-literals -- i just borrowed the delimiter style.)
Data Element
The question specifies data should be stored in HTML. It should require minimal keystrokes.
I didn't want to redundantly retype the fieldnames on every row. I didn't use slotting, JSO, Jason, or XML syntax, because those are all verbose.
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots
It's a simple delimited list. I eliminated all braces, brackets, equals, parens, colons etc.
I put the fieldname-headers only on the first row. The headers are a visual aid for the HTML developer, and a key for Javascript to know the fieldnames and order.
Record Delimiter: End-of-line
Field Delimiter: Double-commas. Seems safe, and they're easy to type. I don't expect to see double-commas in any actual data. Beware, the developer must enter a space for any empty cells, to prevent unintended double-commas. The programmer can easily use a different delimiter if they prefer, as long as they update the Javascript. You can use single-commas if you're sure there will be no embedded commas within a cell.
The data block is hidden using the hidden attribute. No CSS needed.
It's a span to ensure it takes up no room on the page.
JAVASCRIPT
Data
The data is processed by Javascript with two split statements, first on newline delimiter, then on the double-comma delimiter. That puts the whole thing into a 2D array. My JS uses trims to get rid of extra whitespace as needed.
Place-holder Substitution
Handling multiple entries requires plugging each entry into the template.
i went with simple string-replacement instead of string literals.
Multiple Templates
New version which supports multiple templates, and ability to use same template in multiple locations on same page.
https://github.com/johnaweiss/HTML-Micro-Templating
Future
Inspired by #MisterJojo, an earlier version of my solution used template literals to do the substitution. However, that was a bit more complicated and verbose, and seemed to require use of eval. So i switched to .replaceAll. Yet template-literals seems like a more appropriate method for templates, so maybe i'll revisit that.
A future version may adapt to whatever custom field-delimiter the HTML developer uses for the data block.
The dollar-curly delimiter for placeholders is a bit awkward to type. So i'm interested in finding a less awkward non-alpha delimiter that won't conflict with HTML. Considering double-brackets or braces [[NAME]]
Maybe there are simpler ways to pull the data-table into JS.
I've read components work well with <template>, but i didn't go there.
Imo, the JS committee should develop a variable-placeholder feature for <template> tags, and natively accommodate storing the data in HTML. It would be great if something like this solution was part of the rendering engine.
I'm trying to modularize my index.html file by having separate python files. Is there a way to convert say a Python string to HTML code? I know Flask uses Jinja templating and I've used it to extensively, but the kind of work I'm trying to do is as follows:
This is my HTML Code:
<article id="portfolios_RemoteSensing">
<h2 style="text-align:center;"> Machine Learning and Remote Sensing</h2>
<hr>
<div style="text-align:left;">
</div>
</article>
I want to encapsulate this as a python string then I'll jsut reference it through Jinja
x = "HTML CODE ABOVE"
{{ x }}
I hope this makes sense.
Yes, pass x to your template and use {{ x|safe }}.
Also see this for some alternatives.
I am using Mustache Template in my HTML DOM to generate some Dynamic contents.
I am using {{ }} inside HTML tags for that..
Now i want generate non HTML dynamic extension, for that i have {{{ }}}
But i dont know the difference between both.
so, what is the difference between these 2?
See the manual
The most basic tag type is the variable. A {{name}} tag in a basic
template will try to find the name key in the current context. If
there is no name key, the parent contexts will be checked recursively.
If the top context is reached and the name key is still not found,
nothing will be rendered.
All variables are HTML escaped by default. If you want to return
unescaped HTML, use the triple mustache: {{{name}}}.
Consider this handlebars template:
<div>
{{content}}
</div>
And the following data:
{
content: 'foo\r\nbar'
}
And the desired result (what I need):
<div>
foo
bar
</div>
But when compiling the handlebars template with the data I get this:
<div>
foo
bar
</div>
So, how to make handlebars keep indentation when replacing a multi-line value?
Of course, changing the data is not an option.
From the compile reference:
preventIndent: By default an indented partial-call causes the output of the whole partial being indented by the same amount. This can lead to unexpected behavior when the partial writes pre-tags. Setting this option to true will disable the auto-indent feature.
So, it follows that if you're able to set up your template so that content is rendered by a partial, e.g.:
<div>
{{> ContentPartial}}
</div>
Then before it's rendered, register the ContentPartial partial:
Handlebars.registerPartial('ContentPartial', '{{content}}')
then it will preserve indentation. (I actually discovered this feature when the auto-indentation screwed up formatting in my <textarea> and I had to disable it).
It's not possible. Handlebars didn't know anything about the place where data will be embedded. You have line break in data and you get line break in result, there is no and nowhere to get a tab (4 spaces) after foo, have just one line break.
One of the ways to deal with your problem is to use such helper, that knows about the indentation and process passed argument proper way.
See quick example.
Helper:
Handlebars.registerHelper('indent', function (data, indent) {
var out = data.replace(/\n/g, '\n' + indent);
return new Handlebars.SafeString(out);
});
used as:
{{indent foo ' '}}
will produce you multiline output with specified indentation.
Adding to #tobias-j answer, the partial can be written inline with no need to register it on the code.
So, your template can be written as:
{{#*inline "inlineContent"}}
{{content}}
{{/inline}}
<div>
{{> inlineContent}}
</div>
You can see it working on the playground.
I have this anchor link:
{{title}}
Sometimes, this title has some content with + (add operator) like:
"Django + Python"
But when it is directly placed on anchor links, the url delivered will be:
http://127.0.0.1:8080/question/tag/1/1/?list_id=Django + Python
Which will eventually cause problem on the retrieval as the url decoder thought the list_id GET = DjangoPython.
So, does anyone knows how to avoid this issue? Note that I do not want to change anchor links to input buttons.
Instead of
{{ title }}
do
{{title|urlencode}}
Instead of hardcoding the URL and building the querystring in the template by concatenating the title context item, consider simplifying it a bit by using django.core.urlresolvers.reverse() and specifying a named URL from the relevant urls.py file. Makes for a clean design when you don't have to worry about URLs changing! Imagine the headache of all the scattered HREFs you'd have to change, versus simply assigning a context variable from the view set to the result of the reverse call. The querystring can be added nicely in the view too, without having to worry about some of the often strange template parsing rules. Note that the urllib.urlencode function acts completely differently to the template filter of the same name; don't get confused! It can be used as follows:
# If the entry in URLConf has named capture groups such as ?<pk>,
# pass a dict as kwargs parameter instead.
url = reverse('your-named-url', args=(1, 1)) # e.g '/question/tag/1/1/'
# Encodes as form data to 'list_id=Django+%2B+Python'
querystring = urllib.urlencode({'list_id': 'Django + Python'})
context['question_url'] = '{}?{}'.format(url, querystring)
Then, in the template it can be as simple as href="{{ question_url }}". It might seem like more work, but it can pay off very quickly and it's a much better separation of concerns. Normally I'd use the {% url "your-named-url" %} template tag, but it doesn't currently handle querystrings nicely -- as in, not in the same manner as it deals with URL args and kwargs defined in URLConf.