Views
Views are etlua (embedded Lua) templates that render your application's HTML with dynamic data.
Embedded Lua in HTML files
Wrap pages with shared structure
Reusable template fragments
code etlua Syntax
Views use the etlua templating engine which embeds Lua code directly in HTML files.
Template Tags
Three ways to embed Lua in templates
<h1><%= title %></h1>
<p>Hello, <%= user.name %>!</p>
<span>© <%= os.date("%Y") %></span>
HTML entities are escaped for security
<!-- Include partials -->
<%- Partial("header") %>
<!-- Render HTML content -->
<%- html_content %>
Use for trusted HTML content and partials
<% if user then %>
<p>Welcome back, <%= user.name %>!</p>
<% else %>
<p>Please log in.</p>
<% end %>
<% for i, item in ipairs(items) do %>
<li><%= item.name %></li>
<% end %>
Execute Lua code without output
folder_open Directory Structure
Views are organized in the app/views/ directory with a clear structure.
app/views/
├── layouts/ # Page layouts
│ ├── app/
│ │ └── index.html.etlua
│ └── docs/
│ └── index.html.etlua
├── partials/ # Reusable fragments
│ ├── header.html.etlua
│ └── footer.html.etlua
├── welcome/ # Controller views
│ ├── index.etlua
│ └── index+iphone.etlua
├── docs/
│ └── index.etlua
└── 404.etlua # Error pages
layers Layouts
Layouts wrap your page content with shared HTML structure like headers, footers, and assets.
Layout Example
app/views/layouts/app/index.html.etlua
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= title or "My App" %></title>
<link rel="stylesheet" href="<%= PublicPath("/app.css") %>" />
</head>
<body>
<%- Partial("header") %>
<main>
@yield <!-- Page content inserted here -->
</main>
<%- Partial("footer") %>
<%- RefreshPageForDevMode() %>
</body>
</html>
info Key elements:
@yield — marks where page content is inserted
PublicPath() — generates paths to public assets
RefreshPageForDevMode() — enables live reload
widgets Partials
Partials are reusable template fragments that can be included anywhere.
<header class="py-6">
<nav>
<a href="/">My App</a>
<a href="/about">About</a>
</nav>
</header>
<!-- Include a partial -->
<%- Partial("header") %>
<!-- From components folder -->
<%- Partial("components/card") %>
Passing Parameters to Partials
Send data to partials using the second argument
Calling with parameters
<!-- Pass a table of parameters -->
<%- Partial("user_card", {
name = "John Doe",
email = "john@example.com",
role = "Admin"
}) %>
<!-- Pass existing variables -->
<%- Partial("product_item", {
product = product,
show_price = true
}) %>
Accessing in partial
<!-- user_card.html.etlua -->
<div class="card">
<h3><%= name %></h3>
<p><%= email %></p>
<span class="badge"><%= role %></span>
</div>
<!-- With defaults -->
<% role = role or "User" %>
info How it works:
Partial()
or operator to set default values for optional params
view_quilt Content Blocks
Use ContentFor to define content in a view that can be yielded in a layout using YieldContent. This is useful for injecting scripts, styles, or sidebars from specific views.
<% ContentFor("scripts", function() return [[
<script>
console.log("Page specific script");
</script>
]] end) %>
<body>
...
<%- YieldContent("scripts") %>
</body>
data_object Passing Data
Controllers pass data to views through Page() function parameters.
Controller
function app.show()
-- Page(view, layout, view_data, layout_data)
Page("users/profile", "app", {
-- Available in View
user = { name = "John" },
tab = "details"
}, {
-- Available in Layout
title = "User Profile"
})
end
View & Layout
<!-- In View (users/profile.etlua) -->
<h2>Hello, <%= user.name %>!</h2>
<p>Tab: <%= tab %></p>
<!-- In Layout (layouts/app/index.html.etlua) -->
<title><%= title %></title>
smartphone Device-Specific Views
Lua on Beans supports device-specific templates with the +device suffix. Device detection is configured in .init.lua.
Configure Device Detection
.init.lua
function SetDevice()
local user_agent = GetHeader("User-Agent")
Params.request = { variant = "" }
local preg = assert(re.compile("iPhone"))
if preg:search(user_agent) then
Params.request.variant = "iphone"
end
end
info How it works:
SetDevice() is called on each request to detect the device
Params.request.variant determines which view suffix to use
View File Structure
Name templates with +variant suffix
app/views/welcome/
├── index.etlua # Default view (variant = "")
└── index+iphone.etlua # iPhone view (variant = "iphone")
When Params.request.variant is set to "iphone", Lua on Beans serves index+iphone.etlua if it exists, otherwise falls back to index.etlua.
build Helper Functions
Built-in helper functions available in all templates:
| Function | Description | Example |
|---|---|---|
Partial(name, params) |
Include a partial template with optional parameters | Partial("header", { title = "Home" }) |
PublicPath(path) |
Generate public asset URL | PublicPath("/app.css") |
RefreshPageForDevMode() |
Enable live reload in development | RefreshPageForDevMode() |
os.date(format) |
Standard Lua date formatting | os.date("%Y") |
ContentFor(tag, block) |
Define content block for layout | ContentFor("head", fn) |
YieldContent(tag) |
Render defined content block | YieldContent("head") |
Pro Tips
File naming: Use .etlua for content views and .html.etlua for layouts and partials.
Security: Always use <%= %> for user data to prevent XSS attacks. Only use <%- %> for trusted content.
Organization: Group related views in folders matching controller names (e.g., views/users/ for users_controller).