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.
We can define template name via {{define "home"}}, and then load it in other (parent) template via {{template "home"}}.
How I can load template via variable value {{template .TemplateName}}. Or it's impossible?
Unfortunately you can't.
The syntax of the {{template}} action:
{{template "name"}}
The template with the specified name is executed with nil data.
{{template "name" pipeline}}
The template with the specified name is executed with dot set
to the value of the pipeline.
The name of the template to be included is a constant string, it is not a pipeline which could vary during execution based on parameters.
If the allowed syntax would be:
{{template pipeline}}
then you could use something like {{template .TemplName}} but since the syntax only allows a constant string, you can't.
Reasoning from Rob why dynamic template invocation is not allowed (source):
We want the template language to be statically analyzable so the context of a template's invocation is clear, checkable, and lockdownable. If an invocation point is totally dynamic, this can't be done. Similarly, if a template can belong to multiple sets, its context can differ between sets in a way that would require all sets to be analyzed simultaneously. Since both these constraints are easy to work around if you want to, at the cost of losing those static checks in a higher-level package, it seemed wise to control the situation in the base template implementation. A higher-level package, such as a hypothetical HTML-only wrapper, can guarantee no workarounds more easily if the constraints are clear.
Alternative #1: Execute Includable Template First
What you can do is execute the template you would want to include first, and insert the result where you want to include it. You can use special types not to escape the result of the inner template when inserting, for example html.HTML in case of HTML templates.
See this example:
func main() {
t := template.Must(template.New("t").Parse(t))
template.Must(t.New("t1").Parse(t1))
params := struct {
Name string
Value interface{}
}{"t1", nil}
b := bytes.Buffer{}
t.ExecuteTemplate(&b, params.Name, nil)
params.Value = template.HTML(b.String())
t.Execute(os.Stdout, params)
}
const t = `<html><body>
Now I will include template with name: {{.Name}}
{{.Value}}
</body>/html>`
const t1 = `I'm template <b>t1</b>.`
Output:
<html><body>
Now I will include template with name: t1
I'm template <b>t1</b>.
</body>/html>
Try it on the Go Playground.
The result of template t1 was inserted unescaped. If you leave out template.HTML:
params.Value = b.String()
t1 would be inserted escaped, like this:
<html><body>
Now I will include template with name: t1
I'm template <b>t1</b>.
</body>/html>
Alternative #2: Restructure Templates
You can restructure your templates not to be in situations where you would want to include a template with varying names.
Example: you might want to create pages where you have a page template something like this:
<html><body>
Title, headers etc.
{{template .Page}}
Footers
</body></html>
You can restructure it to be something like this:
header template:
<html><body>
Title, headers, etc.
footer template:
Footers
</body></html
And your page templates would include header and footer like this:
{{template "header" .}}
Page content comes here.
{{template "footer" .}}
Alternative #3: Use {{if}} action and predefined names
If you know the template names prior and it is not an exhausting list, you can use the {{if}} template action to include the desired template. Example:
{{if eq .Name "page1"}}
{{template "page1" .}}
{{else if eq .Name "page2"}}
{{template "page2" .}}
...
{{end}}
Alternative #4: Modifying the static template text
The idea here is that you could modify the static text of the outer template manually and insert the name of the inner template you want to include.
The downside of this method is that after inserting the name of the inner template, you have to re-parse the template, so I don't recommend this.
I've a number of 'building blocks' I've created of my own to use when templating a site and I'm wondering how I could use Panini to re-use partials on the same page, with different data.
Say for example I have a partial which basically adds a h1 tag followed by a single p tag but I want to be able to re-use that same partial on the same page with different data each time.
This is the content of the partial file for example;
<h1> {{ h1Header }}</h1>
<p> {{ pParagraph }} </p>
The Frontmatter data in the Index file;
---
h1Header: Hello!
pParagraph: This is some text.
---
And this to call the partial into action;
{{> partial }}
Unless I'm doing something fundamentally wrong the way I'm using it at the moment would mean I'd have to create several different partials for each possible outcome.
I was wondering if there was some way of sending arguments etc. If someone can point me in the right direction with even the simplest of examples just to get a feel of what I can do and what to look into I'd be grateful.
You can pass data to your partials passing a context or parameters to your partial. You can pass different data every-time you render the partial, according to the manual.
Having a partial called test:
<h1>{{foo}}</h1>
You can render it with specific data:
{{> test foo="bar"}}
{{> test foo="foobar"}}
Which results in
<h1>foo</h1>
<h1>foobar</h1>
I hope someone can assist me on this issue.
I am pulling details from a database to display on a twig template (using Symfony2) but the way in which it is saved in the db makes it difficult to interpret the HTML.
Basically, the HTML tags are already translated as entities in the table, e.g.:
<p>Bach Flower Crab Apple Remedy: the "cleansing" Remedy can be used both internally and externally </p><p><strong>
And so on. I have researched the rendering options in twig and tried the following (based on me rendering a loop of product descriptions):
{% set strategy = 'html' %}
{% autoescape 'html' %}
{{ product.description|escape('html')|raw }}
{% endautoescape %}
and also just:
{{ product.description|raw }}
The first method just echoes the existing content (as entities) and the second method just renders the HTML tags to the page as follows:
<p>Bach Flower Crab Apple Remedy: the "cleansing" Remedy can be used both internally and externally.</p><p><strong>...
So, as you can see, I cannot find a way to actually interpret the HTML tags in order to display the description as it should be.
Is there a way to do this? I can't do it in the PHP as all it's doing is sending an object to the template which is looped through:
public function showAction(Request $request, $store_id=0)
{
$max = 1000;
$repository = $this->getDoctrine()->getRepository('AppBundle:Product');
$products = $repository->getProductsByStoreId($store_id,$max);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$products,
$request->query->get('page', 1),
20
);
$return['products'] = $pagination;
$return['categories'] = $this->getCategories();
return $this->render('AppBundle:tables:productstable.html.twig', $return);
}
Your core issue is that you do not have HTML in your database to begin with. At best Twig could be outputting some HTML entities, which will render visibly as "<p>...", and at "worst" Twig will escape the text to render it accurately as it actually is, which is "<p>...". Expecting Twig to output actual HTML which will render a paragraph is unrealistic, since that's not what your original data contains at all.
You'll have to HTML-decode that text in PHP first, and then output it in Twig with ..|raw. raw means that Twig will output it as is without further escaping it. Since it's nonsense to get the data from the database to then html_entity_decode it, you need to fix your data input here! Don't HTML encode data which is going into the database, it serves no purpose.
I think you have to write custom escaper plugin to decode html entities and use it like this:
{{ product.description|myawesomehtmlentitiesdecoder|raw }}
http://twig.sensiolabs.org/doc/filters/escape.html#custom-escapers for reference.
But generally, it's better to store HTML in database and then apply needed security filters on output.
I want to write template function like Smarty's capture.
How can I capture html inside
{{capture}}
...
{{/capture}}
How to do this?
{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}
ONE TWO
You would do it in the application: Use a template for the HTML snippet you want to capture, render it, save it to a string and use it in subsequent template rendering.