Output buffering can be used to create nested templates, allowing for reusable, organized content.
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".
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.
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.
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.
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.