Skip to content

Instantly share code, notes, and snippets.

@abadc0de
Last active December 14, 2015 23:28
Show Gist options
  • Save abadc0de/5165526 to your computer and use it in GitHub Desktop.
Save abadc0de/5165526 to your computer and use it in GitHub Desktop.
Lua page templates for HTML documents, with output buffering

Templating with output buffers

Output buffering can be used to create nested templates, allowing for reusable, organized content.

A simple template

In Lua pages, expect a free global variable (here, page) is a table with strings to inject. The "base" template might look like this:

<? print('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n')
?><!doctype html>
<html><head><title><? print(page.title) ?></title>
  <? print(page.head) ?>
</head>
<body><? print(page.body) ?></body></html>

This template expects global variable page to have title, head, and body members. Let's name it "template/base.lp".

A content page

Suppose we have two main layouts for our pages, one for the home page and one for inner pages. The home page might use the base template directly, and look something like this:

<? ob.push(); page = { title = "My Site" } ?>

<? ob.push() -- head ?>
  <link rel="stylesheet" href="/static/site.css">
  <link rel="stylesheet" href="/static/home.css">
<? page.head = ob.pop() ?>

<? ob.push() -- body ?>
  <h1> My Site </h1>
  <img src="/static/biglogo.png" alt="My Site logo">
  <? mg.include("fragment/navigation.lp") ?>
  ... Some content ...
<? page.body = ob.pop() ?>

<? ob.pop(); mg.include("template/base.lp") ?>

The entire page is wrapped in a buffer, which is discarded. This keeps the whitespace between Lua blocks from getting sent (important because they would be sent before the headers). A global variable page is declared, and the table is populated with the data our main template is looking for. Then, the main template is loaded.

The outer buffer and the template inclusion could be handled by a router script, simplifying this page further. For example, the router could look for a path to a Lua page in page.template after including the requested page, and include the template.

A nested template

Our inner pages all have elements they will share, so instead of using the base template directly, we'll create another template, "template/inner.lp", that piggybacks on our base template:

<? ob.push() -- suppress whitespace ?>

<? ob.push() -- head ?>
  <link rel="stylesheet" href="/static/site.css">
  <link rel="stylesheet" href="/static/inner.css">
  <? print(page.head) ?>
  <script src="/static/inner.js"></script>
<? page.head = ob.pop() ?>

<? ob.push() -- body ?>
  <? mg.include("fragment/navigation.lp") ?>
  <h2><? print(page.title) ?></h2>
  <? print(page.body) ?>
  <? mg.include("fragment/footer.lp") ?>
<? page.body = ob.pop() ?>

<? ob.pop(); mg.include("template/base.lp") ?>

This template looks for the same page fields as our base template, wraps them with more content, and then includes the base template.

A content page, redux

Our inner pages in most cases amount to:

<? ob.push(); page = { title = "About My Site" } ?>

<? ob.push() -- body ?>
  ... My Site is really great, blah blah ...
<? page.body = ob.pop() ?>

<? ob.pop(); mg.include("template/inner.lp") ?>

Or, if we have a router script handling the outer buffer and template:

<? page = { template="template/inner.lp", title = "About My Site" } ?>

<? ob.push() -- body ?>
  ... My Site is now free of redundancy ...
<? page.body = ob.pop() ?>

We might want our home page to be simple like this too, and make a "template/home.lp" just for it.

Next steps

Nested templates can look for more than just title, head, and body sections. Maybe some content pages want to add things to the footer, or maybe several pages share a two-column layout. It's easy for templates to "define" new fields; simply print them, and if they're provided by the page, they'll appear.

This technique can be useful for providing alternate templates for different mediums (PC, mobile, tablet) while reusing the same content. Of course, this assumes some kind of router script that is able to decide which templates to use.

Organizing things this way is also useful for history manipulation; since the content pages only include the relevant content, they can be loaded by XHR or rewritten (by a template) as valid JSONP and included as a script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment