Create a simple site in Origami

A hands-on walkthrough

This tutorial walks you through the basics of Origami in a simple, live project. If you prefer a conceptual introduction first, see Hello, world.

Scenario

One day your team decides:

We need an “About Us” site! The main page should list the people on our team, with thumbnail photos and links to separate pages for each person. A person’s page should show their name and a full-size photo.

Open the sample About Us site and click on a few pages to get a feel for it. (This would typically be part of a larger site, but for illustration purposes we’ll consider it a site on its own.)

If you’re the kind of person who can write spreadsheet formulas, you can use the Origami language to build a site like that.

Start

You can run this tutorial on GitHub Codespaces a popular editor for professional software developers that uses a web-based version of Microsoft Visual Studio Code. Basic GitHub accounts include some free use of Codespaces. As an alternative, you can clone the project locally and use whatever code editor you prefer.

Open the introduction on GitHub.

Create your own copy of the project by clicking the Fork button near the top.

In your newly-created copy of the pattern intro project, click the Code button, then the Codespaces tab, then click Create codespace on main or the + button.

A VS Code project editor will start. Eventually you’ll see a list of files on the left.

Click the Run and Debug button (▷) in the left bar, then ▷ Launch via npm. This will start the small Origami server.

A notification should appear saying, “Your application running on port 5000 is available.” Click the Open in Browser button to open the running tutorial site in a separate window tab.

You’re going to be switching back and forth between these two browser windows. Let’s call the first one the Code window and the new one the Preview window. (In a moment you’ll open a third window.)

The page in the Preview window should say: Hello

Edit a simple page

You’ll define the complete set of pages and other resources your site will need in an Origami file with a .ori extension. This project is configured to serve the site defined in src/site.ori.

In the Code window, click the code Explorer (an icon with two overlapping documents) in the left bar. Expand the src folder and open site.ori:

{
  index.html = "Hello"
}

Everything between the { } curly braces defines the top level of the site. For now, this defines a single thing you can think of as a “file” even though it’s not stored anywhere. The name or key for this file is “index.html”. The contents or value of this file is the text “Hello”.

You’re going to work on this site.ori file so that it creates the About Us site. The index.html page will eventually become the main About Us page.

Try it: Edit the quoted text in the formula for index.html to give it more content, like: Hello, world!

The code editor will auto-save the text.

In the Preview window, refresh the page to see: Hello, world!

View your site as a tree

Origami lets you visualize and explore your site as a hierarchical tree of pages and other resources.

Open the Preview window, copy its URL, then open a new tab, and paste in that URL. Add /!svg to the end of the URL. The new URL should now look like https://your-project-name.app.github.dev/!svg. Press Return.

You’ll see your site visually represented as a tree with just one branch:

g index.html Hello, world! ->index.html index.html

The little circle represents the overall tree, and the box represents the index.html file.

Let’s refer to this browser window as the Tree Diagram window.

In the Tree Diagram window, click the box for index.html to view it.

Click the browser’s Back button to navigate back to the main tree diagram.

You’ll return to the Tree Diagram window occasionally to view the structure of your evolving site and to explore the individual pages.

Switch back to the Code window.

Use a template to create text

The index.html file is currently defined with a short quoted string. You can create larger, more realistic HTML pages using templates. A template is a document with placeholders that will be filled with data.

For this tutorial, you’ll use the template system built into Origami, but Origami can also work with other template systems.

View the file src/greet.ori.

(name) => `<p>Hello, <strong>${ name }</strong>!</p>`

This template defines a function: something that accepts input and produces output. In this case, the function in greet.ori accepts a name and returns an HTML greeting that incorporates that name.

Inside the ` ` backticks, the placeholder marked with ${ } curly braces contains an Origami expression. In this placeholder, the name reference tells Origami to insert the name passed to the template into the HTML at that point.

You can call this template from an Origami formula.

Try it: In site.ori, update the formula for index.html to remove the quoted string, and instead call the greet.ori template and pass it the text "world".

{
  index.html = greet.ori("world")
}

When someone visits index.html, Origami will now generate the HTML for it:

<p>Hello, <strong>world</strong>!</p>

Refresh the Preview window to confirm the page now has “world” in bold: Hello, world!

When you call greet.ori in a formula like this, Origami searches the current scope for that name. Origami will find the src/greet.ori template file and use it to create the home page.

Defining the team data

Data in Origami projects can come from pretty much anything. This sample project stores the data for your team members in a file format called YAML, but it could just as easily use another format called JSON, or some other data file format, or data sitting on a server.

In the Code window, open the team data file in src/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.

This defines an array of person records but this data is too boring!

In teamData.yaml, replace the people’s names with your name and the names of family or friends.

Formulas can extract data

In Origami you can use slash-separated paths to extract information out of any hierarchical source, whether it’s a file system folder or data like your team information.

Try it: In site.ori, update your formula for index.html to pass the name of the first team member to greet.ori. Array indexes start with zero, so /0/name will get the name of the first person.

{
  index.html = greet.ori(teamData.yaml/0/name)
}

Refresh the Preview window to see something like: Hello, Alice!

Incorporate data into your site’s tree

You can incorporate folders and other sources of hierarchical data into your site’s tree. For example, you can include all the data in teamData.yaml into a browsable part of your site.

Try it: In the Code window, update site.ori to add a formula that defines team as equal to teamData.yaml/ (with a trailing slash):

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = teamData.yaml/
}

In the Tree Diagram window, refresh the page to confirm that the tree now includes an team area with all the data from teamData.yaml as navigable parts of the site.

But for the team area to actually be useful, you’ll need to transform that raw data into presentable HTML.

Creating a virtual folder with a map

There are several places in this web site where you want to transform one set of things and into a new set of things:

  1. For each team member in teamData.yaml, you want a page in the team area.
  2. For each team member in teamData.yaml, you want a tile on the main About Us page.
  3. For each image like images/van.jpg, you want a corresponding thumbnail image like thumbnails/van.jpg.

You can address all these situations in Origami with a map in the computer science sense of the word: an operation performed on every item in a collection to produce a new collection.

You can think of the result of a map as a virtual folder — a set of things you can browse and work with, but which aren’t stored anywhere. This is an efficient way to create an entire area of a site from existing data or files.

Let’s start by mapping the people defined in teamData.yaml: for each person, we’ll create a tiny page in the team area.

Try it: In the Code window, update the formula in site.ori for team to:

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = map(teamData.yaml, (person) => person/name)
}

This formula calls a built-in function called map. All built-in functions start with an @ sign.

This team formula says: starting with the tree of structured data in teamData.yaml, create a new tree. For each person in the data, evaluate the expression person/name, which gets the name field of the person being operated on.

If you know JavaScript, such a function is the same as a JavaScript arrow function.

So the team formula transforms the team data into a corresponding tree of just the names:

g 0/ ->0/ 0/ 1/ ->1/ 1/ 2/ ->2/ 2/ 0/name Alice 0/->0/name name 0/image kingfisher.jpg 0/->0/image image 1/name Bob 1/->1/name name 1/image beach.jpg 1/->1/image image 2/name Carol 2/->2/name name 2/image venice.jpg 2/->2/image image
g 0/ Alice ->0/ 0/ 1/ Bob ->1/ 1/ 2/ Carol ->2/ 2/
Tree structure of teamData.yaml
Mapped tree of names

In the Tree Diagram window, refresh the page to confirm that the tree now includes an team area with the names from teamData.yaml.

g index.html <p>Hello, <strong>Alice</strong>!</p> ->index.html index.html team ->team team team/0/ Alice team->team/0/ 0/ team/1/ Bob team->team/1/ 1/ team/2/ Carol team->team/2/ 2/

Use a template in a map

The formula you give to map can be as complex as your situation requires.

Try it: In the Code window, in site.ori, update the team formula so that, instead of just returning a name, it calls the greet.ori template and passes in that person’s name:

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = map(teamData.yaml, (person) => greet.ori(person/name))
}

In the Tree Diagram window, refresh the page to see the updated team area.

g index.html <p>Hello, <strong>Alice</strong>!</p> ->index.html index.html team ->team team team/0/ <p>Hello, <strong>Alice</strong>!</p> team->team/0/ 0/ team/1/ <p>Hello, <strong>Bob</strong>!</p> team->team/1/ 1/ team/2/ <p>Hello, <strong>Carol</strong>!</p> team->team/2/ 2/

Pull in more resources

The src folder has two real subfolders you’ll want to include in the tree for your site:

  • assets contains a stylesheet and icon
  • images contains sample images you can use to represent your team members

You can pull a real folder or file into your tree by writing its name on a line by itself.

Try it: Update site.ori to add lines that pull in the assets and images folders.

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = map(teamData.yaml, =greet.ori(_/name))
  assets
  images
}

Switch to Tree Diagram window and refresh it to see the updated site structure.

Your site now includes both real files (the assets and images) and virtual files (the greetings in the team area).

Formulas can call JavaScript

You can do a lot in Origami without JavaScript, but JavaScript programmers can extend Origami with JavaScript. We’ll briefly look at that; you won’t need to know JavaScript to complete this step.

In the Code window, view the images in the src/images folder. Each person in teamData.yaml identifies one of these full-size images as a profile photo.

For each full-size image, you want to produce a corresponding thumbnail image for the main About Us page. Instead of using an image-editing app to create a real folder of thumbnail images, you can create virtual thumbnail images on demand.

View the file src/thumbnail.js. This contains a small JavaScript function which can invoke an image-processing library to generate a small thumbnail copy of an image.

import sharp from "sharp";

export default function thumbnail(buffer) {
  return buffer !== undefined
    ? sharp(buffer).resize({ width: 200 }).toBuffer()
    : undefined;
}

Try it: In site.ori, add a new formula for small.jpg that calls thumbnail.js as a function and passes in the individual file images/van.jpg.

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = map(teamData.yaml, =greet.ori(_/name))
  assets
  images
  small.jpg = thumbnail.js(images/van.jpg)
}

Switch to the Tree Diagram window and refresh it.

In the tree diagram, click the box for the real image in images/van.jpg to preview it.

Navigate back to the main diagram and click the box for small.jpg to see the same image at a smaller size. The formula you created above produces this thumbnail on demand.

Navigate back to the main diagram.

Create a virtual folder of thumbnails

You could write formulas to create a thumbnail for each image in the images folder — but the Origami map function lets you define the transformation of all the images with a single line.

Try it: Switch to the Code window. In site.ori, delete the formula for small.jpg and replace it with the following thumbnails formula:

{
  index.html = greet.ori(teamData.yaml/0/name)
  team = map(teamData.yaml, =greet.ori(_/name))
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

This thumbnails formula applies the thumbnail.js function to each of the images. In that map function, the second parameter is just the file name thumbnail.js, which is a shorthand for writing the longer form (x) => thumbnail.js(x)

Because Origami treats real folders and virtual folders the same, you can browse your virtual folder of thumbnails.

Switch to the Tree Diagram window and refresh it to view your site’s updated structure.

The virtual thumbnails folder in the diagram now contains a set of thumbnail images that do not exist in any persistent form. They are potential images. If you click on one, it will be created at that moment.

Use a map inside a template

The main About Us page should display a tile for each member that links to their individual page.

In the Code window, open src/index.ori. This template will form the basis of the final home page.

(people) => `<h1>About Us</h1>
<ul>
  ${ map(people, (person) => `
    <li>${ person/name }</li>
  `) }
</ul>
`

In site.ori, you’ve already created a map of images to thumbnails, and a map of team data to a set of greetings. The index.ori template uses the same kind of map to transform the team data into corresponding bits of HTML.

The index.ori file defines two templates, an outer template and an inner template:

  • The outer template spans all lines and defines the overall page. This outer template will accept the entire collection of people data as input.
  • The inner, nested template is defined on the middle line as part of the map. That inner template will receive a single person at a time as input. This template generates a list item containing that person’s name.

Try it: In site.ori, update your index.html formula to remove the call to greet.ori and instead invoke the index.ori template, passing in the list of people in teamData.yaml.

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, =greet.ori(_/name))
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

Refresh the Preview window to see the heading About Us and a bulleted list of names.

A nested template can span multiple lines

The text inside a template can be as complex as you want.

Try it: Copy and paste this fuller template into index.ori:

(people) => `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>About Us</title>
    <link rel="stylesheet" href="assets/styles.css">
  </head>
  <body>
    <header>
      <img class="icon" src="assets/personIcon.svg">
      <h1>About Us</h1>
    </header>
    <ul class="tileGrid">
    ${ map(people, (person) => `
      <li class="tile">
        <a href="team/${ person/name }.html">
          <img
            class="avatar"
            src="thumbnails/${ person/image }"
            alt="${ person/name }"
          >
          <h2 class="name">${ person/name }</h2>
          <div class="location">${ person/location }</div>
        </a>
      </li>
    `) }
    </ul>
  </body>
</html>
`

(Note: the last line contains a backtick character; be sure to copy that too.)

Functionally speaking, this is no more complex than the earlier template; it just has more elements.

Refresh the Preview window to see a tile for each team member that includes their name and location. It also shows a thumbnail image pulled from the virtual thumbnails folder you created earlier. As far as the <img> tag above knows, that thumbnail is a real image — but actually you’re creating that image on demand.

Use a person template

You can use a template for the people pages in the team area too.

In the Code window, view the src/person.ori template:

(person) => `<h1>${ person/name }</h1>`

This template displays a person’s name in a header. You can use this in the map that defines the team area.

Try it: In site.ori, edit the team formula to replace the (person) => greet.ori(person/name) expression with person.ori.

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, person.ori)
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

Refresh the Tree Diagram window to see that the pages in the team area now use your person.ori template.

g 0/ <h1>Alice</h1> ->0/ 0/ 1/ <h1>Bob</h1> ->1/ 1/ 2/ <h1>Carol</h1> ->2/ 2/

Use people names as file names

As you’ve seen, the top-level keys in teamData.yaml are numbers, like 0 for the first person, so at the moment the team area pages are identified with numbers too. But in your final website tree, you’d like the keys in the team area to include the person’s name, like Alice.html.

So you want to transform both the keys and values of the team data. You can do this with an expanded form of the map function.

Try it: In the Code window, edit site.ori and its team formula so that the second parameter is a set of options in { } curly braces. Turn the existing person.ori reference into a value option.

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, {
    value: person.ori
  })
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

This will use person.ori to transform values just as before.

Now add a key option that will change the keys (names) of the team pages:

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, {
    key: (person) => person/name
    value: person.ori
  })
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

Refresh the Tree Diagram window to confirm that the team area is now using names instead of numbers:

g 0/ <p>Hello, <strong>Alice</strong>!<p> ->0/ 0/ 1/ <p>Hello, <strong>Bob</strong>!<p> ->1/ 1/ 2/ <p>Hello, <strong>Carol</strong>!<p> ->2/ 2/
g Alice <p>Hello, <strong>Alice</strong>!<p> ->Alice Alice Bob <p>Hello, <strong>Bob</strong>!<p> ->Bob Bob Carol <p>Hello, <strong>Carol</strong>!<p> ->Carol Carol
Before: pages have numbers
After: pages have names

Add an HTML extension

We want the pages in the team area to end in a .html extension because that helps indicate the type of data the files contain. One way you can do that in an Origami map is defining a key with a small template.

In site.ori, update the team formula’s key option to add a.html extension to the keys.

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, {
    key: (person) => `${ person/name }.html`
    value: person.ori
  })
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

Refresh the Tree Diagram window to confirm that the team pages now have names that end in .html:

g Alice.html <p>Hello, <strong>Alice</strong>!<p> ->Alice.html Alice.html Bob.html <p>Hello, <strong>Bob</strong>!<p> ->Bob.html Bob.html Carol.html <p>Hello, <strong>Carol</strong>!<p> ->Carol.html Carol.html

Fill out the person template

The only thing left to do is complete the person.ori template.

Replace the contents of person.ori with:

(person) => `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>${ person/name }</title>
    <link rel="stylesheet" href="../assets/styles.css">
  </head>
  <body>
    <a class="headerLink" href="..">
      <header>
        <img class="icon" src="../assets/personIcon.svg">
        <h1>About Us</h1>
      </header>
    </a>
    <main class="person">
      <img class="avatar large" src="../images/${ person/image }" alt="${ person/name }">
      <h2 class="name">${ person/name }</h2>
      <div class="location">${ person/location }</div>
      <p class="bio">${ person/bio }</p>
    </main>
  </body>
</html>
`

(Note: the last line contains a backtick character; be sure to copy that too.)

In the Preview window, you can now click a person tile on the main About Us page to navigate to the specific page for that team member.

View the tree of the completed site

The site is now complete.

Switch to the Tree Diagram window and refresh it to view your site’s final structure. In that diagram (not the one below) you can click on the circles or boxes to explore what you’ve made.

g index.html <h1>About Us</h1> ->index.html index.html team/ ->team/ team/ assets/ ->assets/ assets/ images/ ->images/ images/ thumbnails/ ->thumbnails/ thumbnails/ team/Alice.html <h1>Alice</h1> team/->team/Alice.html Alice.html team/Bob.html <h1>Bob</h1> team/->team/Bob.html Bob.html team/Carol.html <h1>Carol</h1> team/->team/Carol.html Carol.html assets/personIcon.svg assets/->assets/personIcon.svg personIcon.svg assets/styles.css assets/->assets/styles.css styles.css images/kingfisher.jpg [binary data] images/->images/kingfisher.jpg kingfisher.jpg images/van.jpg [binary data] images/->images/van.jpg van.jpg images/venice.jpg [binary data] images/->images/venice.jpg venice.jpg thumbnails/kingfisher.jpg [binary data] thumbnails/->thumbnails/kingfisher.jpg kingfisher.jpg thumbnails/van.jpg [binary data] thumbnails/->thumbnails/van.jpg van.jpg thumbnails/venice.jpg [binary data] thumbnails/->thumbnails/venice.jpg venice.jpg

To review, you’ve created this entire site with a few resources, a couple of templates, and a concise site.ori with a handful of formulas:

{
  index.html = index.ori(teamData.yaml)
  team = map(teamData.yaml, {
    key: (person) => `${ person/name }.html`
    value: person.ori
  })
  assets
  images
  thumbnails = map(images, thumbnail.js)
}

Building static files

You have been viewing your About Us site using a small Origami server running in the background. Since the members of your team aren’t going to change every minute, you can turn the whole site into static files: regular files whose contents aren’t expected to constantly change.

Publishing a site as static files is faster and cheaper than running a live web server.

In the Code window, click on the Terminal area at the bottom of page.

In the Terminal, type the following command. (The $ dollar sign comes from the terminal — don’t type it.)

$ npm run build

That copies all the virtual files defined inside site.ori into a real folder called build.

On the left side of the Code window, expand the build folder in the list of files on the left and view the files it contains.

In addition to copies of the real files in the assets and images folders, the build folder now contains real copies of all the virtual files you defined in site.ori:

  • A real thumbnails folder with real thumbnail versions of each image.
  • A real index.html page with HTML that includes a tile for each team member.
  • A real team folder with real HTML pages for each team member.

Because these are regular “static” files, you can host these on many different web hosting services to publish your site. Web hosting is beyond the scope of this tutorial, but one sample web host that’s well suited for hosting static files is Netlify. For small projects with modest traffic, static file hosting on Netlify is often free.

Done!

This concludes the Origami tutorial. If you’d like to try working with Origami on your own machine, you can copy the origami-start project.

You can continue exploring related topics:

  • The Origami expression language you used to write formulas and template expressions has additional features not covered in this tutorial.
  • View some examples of Origami sites.
  • As you were creating the About Us site, the Origami command-line interface and its included web server was working behind the scenes to let you view the site during development and to copy the virtual files to real files.
  • The conceptual framework is built on an async-tree library that lets you do everything that you did here with formulas using JavaScript instead.
  • You can implement sites completely from scratch using the async tree pattern and no library or framework at all, an approach may appeal to people who want to work as close to the metal as possible. That pattern is also a useful reference if you want to understand how Origami works under the hood.

 

Back to Overview