etlua
Templatesetlua
is a templating language that lets you render the result of Lua
code inline in a template file to produce a dynamic output. In Lapis we use
etlua
to render dynamic content inside of HTML templates.
etlua
files use the .etlua
extension. Lapis knows how to load those types
of files automatically using Lua’s require
function after you've enabled
etlua
For example, here’s a simple template that renders a random number:
<!-- views/hello.etlua -->
<div class="my_page">
Here is a random number: <%= math.random() %>
</div>
etlua
comes with the following tags for injecting Lua into your templates:
<% lua_code %>
runs Lua code verbatim<%= lua_expression %>
writes result of expression to output, HTML escaped<%- lua_expression %>
same as above but with no HTML escapingAn action is a function that handles a request that matches a particular route. An action should perform logic and prepare data before forwarding to a view or triggering a render. Actions can control how the result is rendered by returning a table of options.
The render
option of the return value of an action lets us specify which
template to render after the action is executed. If we place an .etlua
file
inside of the views directory, views/
by default, then we can render a
template by name like so:
local lapis = require("lapis")
local app = lapis.Application()
app:enable("etlua")
app:match("/", function()
return { render = "hello" }
end)
return app
lapis = require "lapis"
class App extends lapis.Application
@enable "etlua"
"/": =>
render: "hello"
Rendering "hello"
will cause the module "views.hello"
to load, which will
resolve our etlua
template located at views/hello.etlua
Because it’s common to have a single view for every (or most actions) you can
avoid repeating the name of the view when using a named route. A named route’s
action can just set true
to the render
option and the name of the route
will be used as the name of the template:
local lapis = require("lapis")
local app = lapis.Application()
app:enable("etlua")
app:match("index", "/", function()
return { render = true }
end)
return app
lapis = require "lapis"
class App extends lapis.Application
@enable "etlua"
[index: "/"]: =>
render: true
<!-- views/index.etlua -->
<div class="index">
Welcome to the index of my site!
</div>
Values can be passed to views by setting them on self
in the action. For
example we might set some state for a template like so:
app:match("/", function(self)
self.pets = { "Cat", "Dog", "Bird" }
return { render = "my_template" }
end)
class App extends lapis.Application
@enable "etlua"
"/": =>
@pets = {"Cat", "Dog", "Bird"}
render: "my_template"
<!-- views/my_template.etlua -->
<ul class="list">
<% for i, item in ipairs(pets) do %>
<li><%= item %></li>
<% end %>
</ul>
You'll notice that we don’t need to refer scope the values with self
when
retrieving their values in the template. Any variables are automatically looked
up in that table by default.
Helper functions can be called just as if they were in scope when inside of a
template. A common helper is the url_for
function which helps us generate a
URL to a named route:
<!-- views/about.etlua -->
<div class="about_page">
<p>This is a great page!</p>
<p>
<a href="<%= url_for("index") %>">Return home</a>
</p>
</div>
Any method available on the request object (self
in an action) can be called
in the template. It will be called with the correct receiver automatically.
Additionally etlua
templates have a couple of helper functions only defined in
the context of the template. They are covered below.
A sub-template is a template that is rendered inside of another template. For example you might have a common navigation across many pages so you would create a template for the navigation’s HTML and include it in the templates that require a navigation.
To render a sub-template you can use the render
helper function:
<!-- views/navigation.etlua -->
<div class="nav_bar">
<a href="<%= url_for("index") %>">Home</a>
<a href="<%= url_for("about") %>">About</a>
</div>
<!-- views/index.etlua -->
<div class="page">
<% render("views.navigation") %>
</div>
Note that you have to type the full module name of the template for the first
argument to require, in this case "views.navigation"
, which points to
views/navigation.etlua
. If you happen to also be using MoonScript templates
you can also include them using the render
function.
Any values and helpers available in the parent template are also available in the sub-template.
Sometimes you need to pass data to a sub-template that’s generated during the
execution of the parent template. render
takes a second argument of values
to pass into the sub-template.
Here’s a contrived example of using a sub-template to render a list of numbers:
<!-- templates/list_item.etlua -->
<div class="list_item">
<%= number_value %>
</div>
<!-- templates/list.etlua -->
<div class="list">
<% for i, value in ipairs({}) do %>
<% render("templates.list_item", { number_value = value }) %>
<% end %>
</div>
render(template_name, [template_params])
— loads and renders a templatewidget(widget_instance)
— renders and instance of a Widget