Packages
raxx
0.17.5
1.1.0
1.0.1
1.0.0
1.0.0-rc.3
1.0.0-rc.2
retired
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.18.1
0.18.0
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
retired
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.14
0.14.13
0.14.12
0.14.11
0.14.10
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
0.0.1
Interface for HTTP webservers, frameworks and clients.
Current section
Files
Jump to
Current section
Files
lib/raxx/view.ex
defmodule Raxx.View do
@moduledoc ~S"""
Generate views from `.eex` template files.
Using this module will add the functions `html` and `render` to a module.
To create layouts that can be reused across multiple pages check out `Raxx.Layout`.
## Example
# greet.html.eex
<p>Hello, <%= name %></p>
# layout.html.eex
<h1>Greetings</h1>
<%= __content__ %>
# greet.ex
defmodule Greet do
use Raxx.View,
arguments: [:name],
layout: "layout.html.eex"
end
# iex -S mix
Greet.html("Alice")
# => "<h1>Greetings</h1>\n<p>Hello, Alice</p>"
Raxx.response(:ok)
|> Greet.render("Bob")
# => %Raxx.Response{
# status: 200,
# headers: [{"content-type", "text/html"}],
# body: "<h1>Greetings</h1>\n<p>Hello, Bob</p>"
# }
## Options
- **arguments:** A list of atoms for variables used in the template.
This will be the argument list for the html function.
The render function takes one additional argument to this list,
a response struct.
- **template (optional):** The eex file containing a main content template.
If not given the template file will be generated from the file of the calling module.
i.e. `path/to/file.ex` -> `path/to/file.html.eex`
- **layout (optional):** An eex file containing a layout template.
This template can use all the same variables as the main template.
In addition it must include the content using `<%= __content__ %>`
## Safety
### [XSS (Cross Site Scripting) Prevention](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content)
All content interpolated into a view is escaped.
iex> Greet.html("<script>")
# => "<h1>Greetings</h1>\n<p>Hello, <script></p>"
Values in the template can be marked as secure using the `EExHTML.raw/1` function.
*raw is automatically imported to the template scope*.
# greet.html.eex
<p>Hello, <%= raw name %></p>
### JavaScript
> Including untrusted data inside any other JavaScript context is quite dangerous, as it is extremely easy to switch into an execution context with characters including (but not limited to) semi-colon, equals, space, plus, and many more, so use with caution.
[XSS Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values)
**DONT DO THIS**
```eex
<script type="text/javascript">
console.log('Hello, ' + <%= name %>)
</script>
```
Use `javascript_variables/1` for injecting variables into any JavaScript environment.
"""
defmacro __using__(options) do
{options, []} = Module.eval_quoted(__CALLER__, options)
{arguments, options} = Keyword.pop_first(options, :arguments, [])
{page_template, options} =
Keyword.pop_first(options, :template, Raxx.View.template_for(__CALLER__.file))
page_template = Path.expand(page_template, Path.dirname(__CALLER__.file))
{layout_template, remaining_options} = Keyword.pop_first(options, :layout)
if remaining_options != [] do
keys =
Keyword.keys(remaining_options)
|> Enum.map(&inspect/1)
|> Enum.join(", ")
raise ArgumentError, "Unexpected options for #{inspect(unquote(__MODULE__))}: [#{keys}]"
end
layout_template =
if layout_template do
Path.expand(layout_template, Path.dirname(__CALLER__.file))
end
arguments = Enum.map(arguments, fn a when is_atom(a) -> {a, [line: 1], nil} end)
compiled_page = EEx.compile_file(page_template, engine: EExHTML.Engine)
# This step would not be necessary if the compiler could return a wrapped value.
safe_compiled_page =
quote do
EExHTML.raw(unquote(compiled_page))
end
compiled_layout =
if layout_template do
EEx.compile_file(layout_template, engine: EExHTML.Engine)
else
{:__content__, [], nil}
end
{compiled, has_page?} =
Macro.prewalk(compiled_layout, false, fn
{:__content__, _opts, nil}, _acc ->
{safe_compiled_page, true}
ast, acc ->
{ast, acc}
end)
if !has_page? do
raise ArgumentError, "Layout missing content, add `<%= __content__ %>` to template"
end
quote do
import EExHTML
if unquote(layout_template) do
@external_resource unquote(layout_template)
@file unquote(layout_template)
end
@external_resource unquote(page_template)
@file unquote(page_template)
def render(request, unquote_splicing(arguments)) do
request
|> Raxx.set_header("content-type", "text/html")
|> Raxx.set_body(html(unquote_splicing(arguments)).data)
end
def html(unquote_splicing(arguments)) do
# NOTE from eex_html >= 0.2.0 the content will already be wrapped as safe.
EExHTML.raw(unquote(compiled))
end
end
end
@doc false
def template_for(file) do
case String.split(file, ~r/\.ex(s)?$/) do
[path_and_name, ""] ->
path_and_name <> ".html.eex"
_ ->
raise "#{__MODULE__} needs to be used from a `.ex` or `.exs` file"
end
end
end