Define a shared header, footer, or side bar
Define a base page template
Transform a folder of markdown into HTML
Merge one folder into another
Add an index page to a folder created with a map
Hide parts of a tree
Define a default value
Extract specific resources from a site
Define a shared header, footer, or side bar #
You can create an Origami template that defines shared HTML elements or other content in a single file, which you can then include in other page templates for any page that needs that shared content.
In this example, you’ll create a simple navigation header.
Define a topNav.html
file to hold the shared navigation elements.
<!-- topNav.html -->
<header>
<a href="/">Home</a>
</header>
The above just defines a single link to the home page; add any other elements you want on your pages.
Define some templates for pages that will use the shared navigation. Inside each template, include a reference to ${ topNav.html }
.
Examples:
// about.ori
Tree.indent`
${ topNav.html }
<h2>About Us</h2>
<p>We have fun making websites.</p>
`
// contact.ori
Tree.indent`
${ topNav.html }
<h2>Contact Us</h2>
<p>We'd love to hear from you.</p>
`
In a site.ori
site definition, define HTML pages that use the above templates.
// site.ori
{
about.html = about.ori/
contact.html = contact.ori/
}
When Origami generates a page like about.ori
, the page template incorporates the topNav.html
file into the output.
$ ori site.ori/about.html
<header>
<a href="/">Home</a>
</header>
<h2>About Us</h2>
<p>We have fun making websites.</p>
This technique can be combined with the following one.
Define a base page template for multiple pages #
Most sites define a consistent structure for all their pages that includes basic HTML elements for things like links to spreadsheet, <meta>
tags, and other top-level page elements. You can define this structure in a base template that will be used by other templates.
Create a page.ori
template that will serve as the base template.
// page.ori
(document) => Tree.indent`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>${ document.title }</title>
</head>
<body>
<h1>${ document.title }</h1>
${ document._body }
</body>
</html>
`
This template expects to receive a document object that has a title
property with the document title and a text
property with the body text.
Create an about.ori
template for an About page. This page will call the base page.ori
template as a function, passing in the desired title
and text
for the About page.
// about.ori
page.ori({
title: "About Us"
_body: Tree.indent`
<p>We have fun making websites.</p>
`
})
Create a site.ori file to define your site:
// site.ori
{
about.html = about.ori/
}
When a site visitor asks for about.html
, this will invoke the about.ori
template. That in turn will call the base page.ori
template, which will incorporate the page title and body text into the page structure for the complete page.
$ ori site.ori/about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>About Us</title>
</head>
<body>
<h1>About Us</h1>
<p>We have fun making websites.</p>
</body>
</html>
As a site grows, the base page template can become quite large. For clarity, it can be helpful to separate out pieces of the base template into separate files. For example, if the top navigation area gets complex, you can separate it into a separate top navigation template; see the preceding section.
Transform a folder of markdown into HTML #
You may want to use markdown format to write pages which are primarily text. You can then have Origami transform the markdown pages to HTML.
Create a folder called markdown
to hold your .md
markdown files:
src/
markdown/
about.md
products.md
support.md
site.ori
Your markdown
folder will have the following structure:
In your site definition, add a line that calls the map
to transform all the markdown files using the mdHtml
builtin.
// site.ori
{
pages/ = Tree.map(markdown/, Origami.mdHtml)
}
The map
builtin will use mdHtml
to transform both the keys (names) and values (contents) of the markdown files: the file extension on the keys will change from .md
to .html
, and the values will change from markdown text to HTML.
If you want the pages to appear at a higher level of the site, you can combine this technique with the spread operator; see below.
Merge one folder into another #
Sometimes you want to group a set of pages into a subfolder to keep your source content organized, but have all those pages appear as if they were direct children of some other folder.
For example, if you are an indie website author, you may want to create set of slash pages at the top level of your site for various aspects of you and your interests. You can create these pages in HTML directly (or use markdown; see the preceding section).
It may be useful to group such pages into a subfolder, but them merge them into the top level of your site so the pages have shorter URLs.
Group the pages into a subfolder called slash
:
src/
slash/
about.html
links.html
now.html
index.html
site.ori
If you include the slash
folder as a subfolder of your site:
// site.ori
{
index.html
slash/
}
you will have the following site hierarchy:
This would give you URLs like /slash/now.html
.
Since you want the slash
pages to appear at the top level of your site, use the spread operator to merge the contents of the slash
folder into the site’s top level:
// siteSpread.ori
{
index.html
...slash/
}
which produces the following hierarchy:
With this, all the pages are directly available at the root of the site and URLs like /now.html
.
Add an index page to a folder created with a map #
Suppose you have a folder of posts you’re create with map
, perhaps to transform a folder of markdown into HTML, and you want the resulting virtual folder of HTML to have its own index.html
page. You can use the same spread operator shown above.
In this situation, you’re going to be using your transformed markdown twice: once to create the HTML pages in the pages
area, and a second time for the index page for the pages
area. To make your map
reusable, define it in a separate file called data.ori
// data.ori
Tree.map(markdown/, Origami.mdHtml)
Now create a basic index page template in pagesIndex.ori
:
// pagesIndex.ori
(pages) => Tree.indent`
<ul>
${ Tree.map(pages, (page, name) => Tree.indent`
<li><a href="/pages/${ name }">${ name }</a></li>
`) }
</ul>
`
Then define a site.ori
formula to create the pages
area. This expression will use the spread operator to incorporate all the individual pages. To that set of pages, the index.html
formula will create the index page, passing the set of individual pages to the pagesIndex.ori
template.
// site.ori
{
pages = {
...data.ori
index.html = pagesIndex.ori(data.ori)
}
}
To visualize this operation: data.ori
defines a tree that looks like this.
The pages
definition in site.ori
includes all of that, plus the additional index.html
page:
Hide parts of a tree #
It can be helpful to break down the construction of complex tree values into simpler temporary values that are used but not made publicly available.
One way to accomplish this is to have a tree define a public
value that is later extracted from the tree.
As a trivial example, this program defines a site whose index.html
includes a name
value:
// public.ori
{
name = "Alice"
index.html: `<h1>${ name }</h1>`
}
Along with index.html
, this tree will expose the name
value:
$ ori public.ori/
name: Alice
index.html: <h1>Alice</h1>
But if you don’t want the name
value to be exposed, you can move the portion of the tree you do want to expose into a public
subtree, and then arrange to return only that subtree:
// private.ori
{
name = "Alice"
public = {
index.html = `<h1>${ name }</h1>`
}
}.public
This program effectively defines a closure: inside the public
subtree, the name
property is available for use in calculations.
The .public
at the end of the program returns only the public
subtree, so the internal name
value is not available externally:
$ ori private.ori/
index.html: <h1>Alice</h1>
Define a default value #
You can define a default value for a tree using the spread operator with an arrow function:
// default.ori
{
...() => 0 // Default value is zero
a: 1
b: 2
c: 3
}
This tree returns the indicated value for any defined key, and zero for anything else:
$ ori default.ori/a
1
$ ori default.ori/b
2
$ ori default.ori/x
0
This works as follows:
- The
() => 0
syntax defines a function that returns zero for any input. - The
...
spread operator merges that function into the tree. - Merging the function into the tree implicitly turns it into a tree: asking this tree for a key will call the indicated function, which will return zero.
- The merged tree ends up combining two trees: 1) the tree defined by the
=0
function, and 2) the tree of the explicitly defined keys and values.
When asked to get a key, the merged tree starts by consulting the second tree (of explicit keys/values). If the tree has a value, that will be returned. Otherwise, the merged tree consults the first tree (the function).
The idiom as written above is suitable for shallow trees: the ...
spread operator does a shallow merge, and the () => 0
function defines a shallow tree.
You can adapt this idiom to provide a default value for deep trees using the Tree.deepMerge
function to do the merge and the Tree.constant
builtin to define the default value.
// deepDefault.ori
Tree.deepMerge(
Tree.constant(0) // Default value is zero
{
a: 1
b: {
c: 2
}
}
)
This provides the default value of zero for any level of the tree:
$ ori deepDefault.ori/a
1
$ ori deepDefault.ori/x
0
$ ori deepDefault.ori/b/c
2
$ ori deepDefault.ori/b/y
0
One use for this is to provide a default “Not found” page for a dynamic site:
// Define a site with a custom "Not found" page
Tree.deepMerge(
// Default value. A Response object cannot be served twice, so instead of
// serving a Response object directly, we return a function that creates a
// new Response object. The server will invoke that function to obtain and
// serve a fresh Response.
Tree.constant(() => new Response("I got nothing", { status: 404 }))
// Actual resources
{
index.html: "Home"
a: {
b.html: "B"
}
}
)
Extract specific resources from a site #
The httpstree:
protocol lets you treat a live website as a tree. Since sites don’t generally make their keys (routes) available, you can only use such a tree to obtain values at known routes.
That said, if you know the routes you want to extract from a site, you can combine httpstree:
with Tree.deepMerge
to extract those specific routes.
// extract.ori
Tree.deepMerge(
// The site to get resources from
httpstree://weborigami.org
// The set of resources we want to get
{
index.html: undefined
language: {
index.html: undefined
}
}
)
This merges two trees together:
- The
httpstree:
defines an opaque tree with values but no keys. - The object literal defines a skeleton tree with keys but no defined values.
When this merged tree is asked to enumerate its keys, it will return the keys from the second tree. When the merged tree is later asked for the values of those keys, it will look for those in the second tree – but since that tree has no defined values, the values from the first tree (the site) will be used.
The result is the tree of explicitly-requested resources from the site:
$ ori extract.ori/
index.html: [contents of index.html]
language:
index.html: [contents of language/index.html]
This technique is useful when you know the set of resources you want to fetch. If you don’t know what resources the site provides, the dev:crawl
builtin can return the complete publicly-reachable set of resources.