Origami templates let you convert turn data into HTML or other text documents through expressions embedded in text.
- These templates work directly on a wide range of data types, including file system folders or network resources.
- Templates have a very small number of fundamental features that can be combined to address a wide range of scenarios.
- You can extend what’s possible in a template expression in JavaScript with essentially no configuration.
You can also use Origami with other template systems. For a reference example, see the Origami Handlebars extension that defines a handler for Handlebars templates.
Template files
An Origami template file is an Origami file (a text file with a .ori
extension) defining an Origami expressions that either evaluates to a text string or to a function that returns text.
Origami templates come in two forms.
Shorthand form
In the shorthand form, you define an Origami template literal using an =
equals sign and backtick characters. Inside the backticks, placeholders marked with ${ }
contain additional Origami expressions whose results are included in the final text:
// greet.ori
=`Hello, ${ _/name }.`
The _
underscore represents the template’s input, so the expression _/name
will get the name
property of any object passed to the template:
# alice.yaml
name: Alice Andrews
$ ori "greet.ori(alice.yaml)"
Hello, Alice Andrews.
Long form with named parameters
You can also define templates with a longer form in which you name the parameters to the template. This form begins with a list of named parameters in parentheses, and uses =>
instead of a plain =
equals sign:
// heading.ori
(text) => `<h1>${ text }</h1>`
$ ori "heading.ori('About Us')"
<h1>About Us</h1>
Here, the text
parameter will have whatever value is passed to the template.
Naming the parameter can help you document the template’s expected input. This also allows you to define multiple parameters.
Template documents
If a template is long and mostly text, it may be more convenient for you to define it as a template document.
Reference local files
You can reference local files in Origami expressions. Depending on the situation, you may not have to pass any arguments to the template — it may be able obtain whatever it needs from its file system context.
If copyright.txt
contains:
©2024 Alice Andrews
Then an Origami template can reference that local file directly:
// fileRef.ori
=`This project is ${ copyright.txt }.`
Invoking the template will inline that file:
$ ori "fileRef.ori()"
This project is ©2024 Alice Andrews.
In cases like this, where the template does not require any argument, in the command line you can avoid the need to quote parentheses by invoking the template with a trailing slash:
$ ori fileRef.ori/
This project is ©2024 Alice Andrews.
Reference trees
If a template expression results in a tree such as a folder or hierarchical data, Origami will collect the deep values of that tree, convert them to strings, then concatenate them.
# greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
// flatten.ori
`Here are the text strings in the greetings tree: ${ greetings.yaml/ }`
$ ori flatten.ori/
Here are the text strings in the greetings tree: Hello, Alice.Hello, Bob.Hello, Carol.
This feature forms the basis for more complex ones (like maps, below), but one basic use for it is to inline a set of files. For example, you might create a folder that contains a collection of HTML fragments as separate files:
$ ls fragments
a.html b.html c.html
$ cat fragments/a.html
<p>A</p>
You can then reference that fragments
folder in a template to concatenate all those HTML fragments into the output:
// concat.ori
=indent`
Here are the fragments concatenated together:
${ fragments }
`
$ ori concat.ori/
Here are the fragments concatenated together:
<p>A</p>
<p>B</p>
<p>C</p>
Use template expressions in any file type
It may be useful to embed Origami expressions inside other kinds of files, such as .html files. You can evaluate such expressions with the built-in inline
function.
For example, you can use this to inline resources such as stylesheets.
<!-- inline.html -->
<!DOCTYPE html>
<html>
<head>
<style>
${ inline.css }
</style>
</head>
<body>
This text will be red.
</body>
</html>
/* inline.css */
body { color: red }
$ ori inline inline.html
<!DOCTYPE html>
<html>
<head>
<style>
body { color: red }
</style>
</head>
<body>
This text will be red.
</body>
</html>
Here, the inline.html
file is acting as an Origami template, but keeps the .html
extension so that it can be otherwise treated as an HTML file.
If the input document contains any front matter (see below), inline preserves this in the output.
Traverse into data
Inside a template, you can use slash-separated paths to traverse into data.
# teamData.yaml
- name: Alice
image: van.jpg
location: Honolulu
bio: After working as a manager for numerous startups over the years, I
decided to take the plunge and start a business of my own.
// teamLead.ori
`The leader of our team is ${ teamData.yaml/0/name }.`
$ ori teamLead.ori/
The leader of our team is Alice.
Reference network resources
Since https
and http
URLs are valid Origami expressions, you can incorporate network content into a template’s output.
// net.ori
=`This content came from weborigami.org: ${ https://weborigami.org/samples/templates/net.txt }`
$ ori net.ori/
This content came from weborigami.org:
Hello, Origami!
This includes being able to traverse into data from the network. A teamData.yaml file posted on the network can be referenced as an expression and then further traversed:
// netData.ori
=`Bob lives in ${
(https://weborigami.org/samples/templates/teamData.yaml)/1/location
}.`
$ ori netData.ori/
Bob lives in Los Angeles.
You can also obtain a data file from the network, treat it as a tree, and map the tree to text. This allows you to directly process network data into text in a template.
Conditions
If your template should output one thing or another depending on a condition, you can use the conditional operator.The general form looks like:
<condition> ? <result if true> : <result if false>
The condition will be evaluated and the appropriate result will be returned.
For example, this template accepts a input
argument that may or may not have a rating
property. The template uses the conditional operator.
// condition.ori
(input) => input/rating
? `Rating: ${ input/rating }`
: `Not yet rated`
If the input
does have a rating, the template shows the rating, otherwise it shows “Not yet rated”.
$ ori “condition.ori({ rating: 3 })”
Rating: 3
$ ori “condition.ori({})”
Not yet rated
A particularly kind of condition that often arises in templates is providing a default value for some field that might not exist in the template. For that particular kind of condition, you can use a “nullish coalescing operator” whose general form is:
<thing that might not exist> ?? <default result>
If the thing exists, the template will output that, otherwise it will output the default result. For an example, see “Processing input front matter” below.
Call your own JavaScript functions
Your template expressions can call any JavaScript in scope via the base of the JavaScript file name.
For example, if you have a file named uppercase.js
in the same directory as the template, a template expression can reference that module’s default export as uppercase
:
// uppercase.js
export default (x) => x.toString().toUpperCase();
// callJs.ori
`Hello, ${ uppercase.js("world") }!`
$ ori callJs.ori/
Hello, WORLD!
If the function you invoke is asynchronous, its result will be awaited before being incorporated into the text output.
Call another template as a function
One template can invoke another as a function.
We can define a template stars.ori
as a component that displays a star rating:
// stars.ori
(number) => `<span class="stars">${ repeat(number, "★") }</span>`
This template repeats a ★ star character for the number of times defined in in the input value. For example, you can directly invoke and test this template, passing in a value of 3:
$ ori stars.ori 3
<span class="stars">★★★</span>
This stars.ori
template defines a function that you can invoke inside expressions in other templates:
// review.ori
=`I give it ${ stars.ori(5) }`
$ ori review.ori/
I give it <span class="stars">★★★★★</span>
This technique can let you define components in plain HTML and CSS.
Wrap one template with another
Another application of invoking a template as a function is to wrap the output of one template inside another. For example, you can create an overall page template for a site called page.ori
:
// page.ori
(contents) => indent`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My Site</title>
</head>
<body>
${ contents }
</body>
</html>
`
A template for a specific type of page, like a contact.ori
template for a Contact Us page, can invoke page.ori
as a function:
// contact.ori
=page.ori(indent`
<h1>Contact Us</h1>
<p>We'd love to hear from you!</p>
`)
Evaluating the contact page template passes its HTML fragment to the overall site page template:
$ ori contact.ori/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My Site</title>
</head>
<body>
<h1>Contact Us</h1>
<p>We'd love to hear from you!</p>
</body>
</html>
Processing input front matter
An input document can define front matter.
Both a template’s input document and the template itself can contain front matter in YAML or JSON format. The front matter is delineated with both a leading and trailing line of three hyphens (---
), like so:
Example: a blog post can be stored as a markdown file with front matter that defines a title
property.
<!-- post1.html -->
---
title: The First Post
---
Here's the text of my first post.
And a template can then reference this title
property. Here the template uses the ??
operator to provide a default title if the input document has no title
.
// blogPost.ori
(post) => indent`
<!DOCTYPE html>
<html>
<head>
<title>${ post/title ?? "A blog post" }</title>
</head>
<body>
${ post/@text }
</body>
</html>
`
Applying the template the blog post includes the document’s title
property as desired:
$ ori blogPost.ori posts/post1.html
<!DOCTYPE html>
<html>
<head>
<title>The First Post</title>
</head>
<body>
Here's the text of my first post.
</body>
</html>
If the template is applied to a post that has no title
, the default title is used:
$ ori blogPost.ori posts/post2.html
<!DOCTYPE html>
<html>
<head>
<title>A blog post</title>
</head>
<body>
Here's the text of my second post.
</body>
</html>
Map trees to text
It’s common to have a template generate some fragment of text for each value in a tree: an array, a set, a folder, etc.
You can handle such cases in Origami templates by calling the built-in map
function to map a tree’s values to text.
# teamData.yaml
- name: Alice
image: van.jpg
location: Honolulu
bio: After working as a manager for numerous startups over the years, I
decided to take the plunge and start a business of my own.
- name: Bob
image: kingfisher.jpg
location: Los Angeles
bio: Having been an art student for 11 years, I constantly explore various
disciplines to incorporate artistic pursuits into product design studies.
- name: Carol
image: venice.jpg
location: Venice
bio: I open the line of communication between clients, customers, and
businesses to get projects done.
// teamList.ori
=indent`
<ul>
${ map(teamData.yaml, (person) => indent`
<li>${ person/name }</li>
`) }
</ul>
`
$ ori teamList.ori/
<ul>
<li>Alice</li>
<li>Bob</li>
<li>Carol</li>
</ul>
The teamList.ori
file defines an outer template that includes an <ul>
tag. Inside that, a substitution calling map
appears, which maps the array of people in teamData.yaml
to a set of HTML fragments using a nested template with an <li>
tag.
How maps work
Origami templates don’t treat such maps specially. Rather, the map
function is returning a tree of HTML fragments that are concatenated into the text output.
In the above example, the map
function maps an array of people to HTML fragments. The transformation can be visualized like this:
Per the discussion in Reference trees, the template concatenates the HTML fragments into the text output.
Reference the key for a value
When map
calls a template, it passes three parameters: the value being mapped, the key for that value, and the overall tree being mapped. You don’t have to use all the parameters.
The key parameter can be useful when you are building a list or index. For example, suppose you want to build an index page for a blog, where you will write posts in a posts
folder:
$ ls posts
post1.html post2.html
You can create an index page that links to these files using the key parameter. Here, the fileName
parameter will end up holding the name of the file being mapped:
// blogIndex.ori
(posts) => indent`
<ul>
${ map(posts, (post, fileName) => indent`
<li>
<a href="posts/${ fileName }">
${ post/title ?? "A blog post" }
</a>
</li>
`) }
</ul>
`
This lets a link reference a file’s specific file name in the href
attribute.
This index page template defines a default title
property to use if a page omits a title; see template and input front matter above.
Evaluating this template produces a list of links to each post, with each href
attribute referencing the appropriate file:
$ ori blogIndex.ori posts
<ul>
<li>
<a href="posts/post1.html">
The First Post
</a>
</li>
<li>
<a href="posts/post2.html">
A blog post
</a>
</li>
</ul>