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. If the code is an expression then the result is ignored<%= lua_expression %> — writes result of expression to output, HTML escaped<%- lua_expression %> — writes result of expression to output, with no HTML escaping. See Security ConsiderationsIf you are displaying user-provided data in HTML then you must take special
care to escape the data when rendering to prevent cross-site scripting
attacks. etlua is fundamentally a system for combining strings, and makes no
guarantee that the HTML generated is valid or secure. It’s your
responsibility to verify that valid markup is generated by using the correct
template tags in the correct locations.
If a malicious user is able to inject JavaScript or other unapproved markup into your page then they may be able to comprise your platform for other users, including stealing sessions or performing unapproved authenticated actions.
The etlua tag <%= lua_expression %> will HTML escape the output such that it
is suitable for use in the content or attributes of an HTML tag.
In some cases it may be cumbersome to use <%= lua_expression %> in multiple
places when constructing HTML elements. The element function can be used to
write a tag to the buffer programatically in Lua code. It will automatically
escape any values passed to it and generate valid markup.
In this example the element function is used to generate the link to the
user, with the username and URL correctly escaped.
<ul class="list">
<% for i, user in ipairs(users) do %>
<li>
<% element("a", { href = url_for(user) }, user:get_display_name()) %>
</li>
<% end %>
</ul>
Notice how element uses <% %> etlua tags. element does not return any
value, but instead writes directly to the buffer.
An 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 (Configured by the
application views_prefix), then we can render a template by name like so:
<!-- views/hello.etlua -->
<div class="hello">
Welcome to my site!
</div>
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. This works because
enable("etlua") installs a custom package loader that is aware of .etlua
files and will convert them into Lua modules that implement the interface
necessary to be used as a view in Lapis.
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")
-- notice route name of `hello` has been added
app:match("hello", "/", function()
return { render = true }
end)
return app
lapis = require "lapis"
class App extends lapis.Application
@enable "etlua"
-- notice route name of `hello` has been added
[hello: "/"]: =>
render: true
Data prepared in an action can be passed the view by storing it on self. For
example we might set some state for our template to render:
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. (The views_prefix is only used when an application
is specifying a template to use)
The render() function can take any renderable object. This means you can
use Widget classes or etlua templates. When a
string is provided as the object to render, it will be loaded with require().
Any values and helpers available in the parent template are made available in the scope of the rendered sub-template.
If data needs to be passed to a sub-template, render takes an optional second
argument of a table of fields to pass down.
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>
etlua template functionsThe following functions are globally available in any etlua template loaded
by Lapis to be used as a view.
render(template_name, [template_params]) — loads and renders a template to the bufferwidget(widget_instance) — renders and instance of a Widget to the bufferelement(name, ...) — renders an HTML element to the buffer with name, supporting the full HTML builder syntax for any nested functionsNote that when a helper renders to the buffer, there will be no return value. It is not necessary to use an etlua tag that will take print the output of the function.
EtluaWidget referenceLapis transparently converts .etlua files to EtluaWidgets when you request
them to be used as a template (after enabling etlua). You can manually
compile template code programatically by interacting directly with the
EtluaWidget class.
It is not necessary to enable etlua if you are using the EtluaWidget
class directly. Instances of the EtluaWidget implement the render interface
necessary to be used in any place Lapis expects a template or view.
Note that etlua templates are compiled to enable them to render at the
highest possible performance. You should avoid compiling templates (eg.
EtluaWidget:load()) during every request or it may have a negative impact on
your performance. Cache the result as a Lua module or somewhere where it can
persist between requests.
EtluaWidget:load(template_code)The load method takes a etlua template string, compiles it and creates a new
EtluaWidget class that can be used to render the template with parameters.
local etlua = require("lapis.etlua")
local MyWidget = etlua.EtluaWidget:load([[
<h1>Hello <%= username %></h1>
]])
local w = MyWidget({ username = "Garf" })
print(w:render_to_string()) --> <h1>Hello Garf</h1>
import EtluaWidget from require "lapis.etlua"
widget = EtluaWidget\load [[
<h1>Hello <%= username %></h1>
]]
widget(username: "Garf")\render_to_string!
EtluaWidget([opts])The default constructor of the widget class will copy every field from the
opts argument to self, if the opts argument is provided. Values on self
will be available in scope for the template when it is rendered.
etluawidget:render_to_string()Renders the template and returns the string result. This will automatically create a temporary buffer for the duration of the render.
etluawidget:render(buffer, ...)Renders the template to the provided buffer. Under normal circumstances it is not necessary to use this method directly.
This method returns nothing.