This guide is focused on using builder syntax in Lua/MoonScript to generate HTML. If you're interested in a more traditional templating system see the etlua Templates guide. This mechanism for generating HTML code is mainly intended to be used with Moonscript, but it can also be used in Lua code.
HTML templates can be written directly as MoonScript (or Lua) code. This is a very powerful feature (inspired by Erector) that gives us the ability to write templates with high composability and also all the features of MoonScript or Lua. No need to learn any goofy templating syntax with arbitrary restrictions.
In the context of a HTML renderer, the environment exposes functions that create HTML tags. The tag builder functions are generated on the fly as you call them. The output of these functions is written into a buffer that is compiled in the end and returned as the result
Here are some examples of the HTML generation:
div! -- <div></div>
b "Hello World" -- <b>Hello World</b>
div "hi<br/>" -- <div>hi<br/></div>
text "Hi!" -- Hi!
raw "<br/>" -- <br/>
element "table", width: "100%", -> -- <table width="100%"></table>
div class: "footer", "The Foot" -- <div class="footer">The Foot</div>
input required: true -- <input required/>
div -> -- <div>Hey</div>
text "Hey"
div class: "header", -> -- <div class="header"><h2>My Site</h2>
h2 "My Site" -- <p>Welcome!</p></div>
p "Welcome!"
The element
function is a special builder that takes the name of tag to
generate as the first argument followed by any attributes and content.
The HTML builder methods have lower precedence than any existing variables, so
if you have a variable named div
and you want to make a <div>
tag you'll
need to call element "div"
.
If you want to create a
<table>
or<select>
tag you'll need to useelement
because Lua uses those names in the built-in modules.
All strings passed to the HTML builder functions (attribute names, values, or tag contents) are escaped automatically. You never have to worry about introducing any cross site scripting vulnerabilities.
The class
attribute can be passed as a table, and the class list will be
constructed from it. The table can contain either array element, or hash
elements:
div {
class: {"one", "two", three: false, four: true}
}, "Hello world!"
Will generate:
<div class="one two four">Hello world!</div>
In addition to the tag functions, a few other helper functions are also available:
raw(str)
— outputs the argument, a string, directly to the buffer without escaping.capture(func)
— executes the function argument in the context of the HTML builder environment, returns the compiled result as a string instead of writing to buffer.text(args)
— outputs the argument to the buffer, escaping it if it’s a string. If it’s a function, it executes the function in HTML builder environment. If it’s a table, it writes each item in the tablewidget(SomeWidget)
— renders another widget in the current output buffer. Automatically passes the enclosing contextrender(template_name)
— renders another widget or view by the module name. Lets you render etlua templates from inside builderIf we want to generate HTML directly in our action we can use the @html
method:
"/": =>
@html ->
h1 class: "header", "Hello"
div class: "body", ->
text "Welcome to my site!"
The environment of the function passed to @html
is set to one that support
the HTML builder functions described above. The return value of the @html
method is the generated HTML as a string. Returning this from the action allows
us to render send it right to the browser
The preferred way to write HTML is through widgets. Widgets are classes who are
only concerned with outputting HTML. They use the same syntax as the @html
helper shown above for writing HTML.
When Lapis loads a widget automatically it does it by package name. For
example, if it was loading the widget for the name "index"
it would try to
load the module views.index
, and the result of that module should be the
widget.
This is what a widget looks like:
-- views/index.moon
import Widget from require "lapis.html"
class Index extends Widget
content: =>
h1 class: "header", "Hello"
div class: "body", ->
text "Welcome to my site!"
The name of the widget class is insignificant, but it’s worth making one because some systems can auto-generate encapsulating HTML named after the class.
The render
option key is used to render a widget. For example you can render
the "index"
widget from our action by returning a table with render set to
the name of the widget:
"/": =>
render: "index"
If the action has a name, then we can set render to true
to load the widget
with the same name as the action:
[index: "/"]: =>
render: true
By default views.
is prepended to the widget name and then loaded
using Lua’s require
function. The views
prefix can be customized by
overwriting the views_prefix
member of your application subclass:
class Application extends lapis.Application
views_prefix: "app_views"
-- will use "app_views.home" as the view
[home: "/home"]: => render: true
Any @
variables set in the action can be accessed in the widget. Additionally
any of the helper functions like @url_for
are also accessible.
-- app.moon
class App extends lapis.Application
[index: "/"]: =>
@page_title = "Welcome To My Page"
render: true
-- views/index.moon
import Widget from require "lapis.html"
class Index extends Widget
content: =>
h1 class: "header", @page_title
div class: "body", ->
text "Welcome to my site!"
Widgets can also be rendered manually by instantiating them and calling the
render_to_string
method.
Index = require "views.index"
widget = Index page_title: "Hello World"
print widget\render_to_string!
If you want to use helpers like @url_for
you also need to include them in the
widget instance. Any object can be included as a helper, and its methods will
be made available inside of the widget.
html = require "lapis.html"
class SomeWidget extends html.Widget
content: =>
a href: @url_for("test"), "Test Page"
class extends lapis.Application
[test: "/test_render"]: =>
widget = SomeWidget!
widget\include_helper @
widget\render_to_string!
You should avoid rendering widgets manually when possible. When in an action
use the render
request option. When in
another widget use the widget
helper function. Both of these methods will
ensure the same output buffer is shared to avoid unnecessary string
concatenations.
Whenever an action is rendered normally the result is inserted into the
current layout. The layout is just another widget, but it is used across many
pages. Typically this is where you would put your <html>
and <head>
tags.
Lapis comes with a default layout that looks like this:
html = require "lapis.html"
class DefaultLayout extends html.Widget
content: =>
html_5 ->
head -> title @title or "Lapis Page"
body -> @content_for "inner"
Use this as a starting point for creating your own layout. The content of your
page will be injected in the location of the call to @content_for "inner"
.
We can specify the layout for an entire application or specify it for a
specific action. For example, if we have our new layout in views/my_layout.moon
class extends lapis.Application
layout: require "views.my_layout"
If we want to set the layout for a specific action we can provide it as part of the action’s return value.
class extends lapis.Application
-- the following two have the same effect
"/home1": =>
layout: "my_layout"
"/home2": =>
layout: require "views.my_layout"
-- this doesn't use a layout at all
"/no_layout": =>
layout: false, "No layout rendered!"
As demonstrated in the example, passing false will prevent any layout from being rendered.
@@include(other_class)
Class method that copies the methods from another class into this widget. Useful for mixin in shared functionality across multiple widgets.
class MyHelpers
item_list: (items) =>
ul ->
for item in *items
li item
class SomeWidget extends html.Widget
@include MyHelpers
content: =>
@item_list {"hello", "world"}
@content_for(name, [content])
content_for
is used for sending HTML or strings from the view to the layout.
You've probably already seen @content_for "inner"
if you've looked at
layouts. By default the content of the view is placed in the content block
called "inner"
.
You can create arbitrary content blocks from the view by calling @content_for
with a name and some content:
class MyView extends Widget
content: =>
@content_for "title", "This is the title of my page!"
@content_for "footer", ->
div class: "custom_footer", "The Footer"
You can use either strings or builder functions as the content.
To access the content from the layout, call @content_for
without the content
argument:
class MyLayout extends Widget
content: =>
html ->
body ->
div class: "title", ->
@content_for "title"
@content_for "inner"
@content_for "footer"
If a string is used as the value of a content block then it will be escaped
before written to the buffer. If you want to insert a raw string then you can
use a builder function in conjunction with the raw
function:
@content_for "footer", ->
raw "<pre>this wont' be escaped</pre>"
@has_content_for(name)
Checks to see if content for name
is set.
class MyView extends Widget
content: =>
if @has_content_for "things"
@content_for "things"
else
div ->
text "default content"
html = require "lapis.html"
render_html(fn)
Runs the function, fn
in the HTML rendering context as described above.
Returns the resulting HTML. The HTML context will automatically convert any
reference to an undefined global variable into a function that will render the
appropriate tag.
import render_html from require "lapis.html"
print render_html ->
div class: "item", ->
strong "Hello!"
escape(str)
Escapes any HTML special characters in the string. The following are escaped:
&
— &
<
— <
>
— >
"
— "
'
— '