@title ErlyWeb: The Erlang Twist on Web Frameworks
@author Yariv Sadan
@copyright Yariv Sadan 2006-2007
@doc
== Contents ==
{@section Introduction}
{@section Directory Structure}
{@section Components}
{@section Models}
{@section Controllers}
{@section Views}
{@section Containers}
{@section The App Controller}
{@section Yaws Configuration}
== Introduction ==
ErlyWeb is a component-oriented web development framework that
simplifies the creation of web applications in Erlang according to
the tried-and-true MVC pattern. ErlyWeb's goal is to let web developers
enjoy all the benefits of Erlang/OTP while
creating web applications with simplicity, productivity and
fun.
ErlyWeb is designed to work with Yaws, a
high-performance Erlang web server. For more information on Yaws, visit
[http://yaws.hyber.org].
== Installation ==
To install ErlyWeb, obtain the latest zip file from [http://erlyweb.org]
and unzip it in your root Erlang code path. This path varies depending on your
operating system (on OS X, it's '/usr/local/lib/erlang/lib'). If you don't know
your path, you can discover it by starting the Erlang shell and calling
"code:lib_dir()." (note that the last directory of the code path always
erlang/lib).
== Directory Structure ==
ErlyWeb applications have the following directory structure:
```
[AppName]/
src/ contains non-component source files
[AppName]_app_controller.erl the app controller
[AppName]_app_view.rt the app view
components/ contains controller, view and
model source files
www/ contains static assets
ebin/ contains compiled .beam files
'''
where AppName is the name of the application. The 'src', 'components'
and 'www' directories may contain additional subdirectories, whose contents
are of the same type as those in the parent directory.
== Components ==
An ErlyWeb component is made of a controller and a view, both of which are
Erlang modules. Controllers
may use one or more models, but this isn't required.
(Technically, a component can be implemented without a view, but
most components have views.)
Controllers are implemented in Erlang source files. Views are typically
implemented in ErlTL files,
but views can be implemented in plain Erlang as well.
The controller file is named '[ComponentName]_controller.erl' and the view
file is named '[ComponentName]_view.[Extension]', where [ComponentName] is
the name of the component, and [Extension] is 'erl' for Erlang files and
'et' for ErlTL files.
When ErlyWeb receives a request such as `http://hostname.com/foo/bar/1/2/3',
ErlyWeb interprets it as follows: 'foo' is the component name, 'bar' is the
function name, and `["1", "2", "3"]' are the parameters (note that
parameters from browser requests are always strings).
When only the component's name is present, ErlyWeb assumes the request is
for the component's 'index' function and with no parameters (i.e.
`http://hostname.com/foo' is equivalent to `http://hostname.com/foo/index').
If the module 'foo_controller' exists and it exports the function
'bar/4', ErlyWeb invokes the function foo_controller:bar/4
with the parameters `[A, "1", "2", "3"]', where A is the Arg record
that Yaws passes into the appmod's out/1 function. (For more information,
visit [http://yaws.hyber.org/].)
Since ErlyWeb 0.7, you can use the catch_all/2 hook to override the
default behavior, i.e. treating the second token as a function name
instead of as parameter. For more information, see {@section catch_all/2}.
== Models ==
ErlyWeb treats all Erlang modules whose names don't end with '_controller'
or '_view' and whose files exist under 'src/components' as models.
If such modules exist, erlyweb:compile()
passes their names to erlydb:code_gen()
in order to generate their ErlyDB database abstraction functions.
If your application uses ErlyDB for database abstraction, you
have to call erlydb:start() before erlyweb:compile() (otherwise,
the call to erlydb_codegen() will fail).
If you aren't using ErlyDB, don't keep any
models in 'src/components' and then you won't have to call erlydb:start().
== Controllers ==
Controllers contain most of your application's logic. They are the glue
between Yaws and your applications models and views.
Controller functions always accept the Yaws Arg for the request as the
first parameter, and they may return any of the values that Yaws
appmods may return (please read
the section below on the `{response, Elems}' tuple to avoid
running into trouble). In addition, controller functions may return
a few special values, which are listed below with their meanings
(note: 'ewr' stands for 'ErlyWeb redirect' and 'ewc' stands for 'ErlyWeb
Component.').
`ewr'
Redirect the browser to the application's root url.
`{ewr, ComponentName}'
Redirect the browser to the component's default ('index')
function
`{ewr, ComponentName, FuncName}'
Redirect the browser to the URL for the given component/function combination.
`{ewr, ComponentName, FuncName, Params}'
Redirect the browser to the URL with
the given component/function/parameters combination.
`{data, Data}'
Call the view function, passing into it the Data variable as a parameter.
`{ewc, A}'
Analyze the Yaws Arg record 'A'. If the request matches a component,
render the component and send the result to the browser.
Otherwise, return `{page, Path}', where Path is the Arg's appmoddata field.
`{ewc, ComponentName, Params}'
Render the component's 'index' function with the given parameters
and send the result to the view function.
`{ewc, ComponentName, FuncName, Params}'
Render the component's function with the given parameters, and send
the result to the view function.
`{ewc, ControllerModule, ViewModule, FuncName, Params}'
This tuple lets you specify exacly which controller and
view modules ErlyWeb uses to render the sub-component. It is considered for
advanced uses only.
`{replace, Ewc}'
This tuple, which was introduced in ErlyWeb 0.6, tells ErlyWeb to
render a different component from the requested one (the new component, `Ewc',
can be described using any of the 'ewc' tuples listed above).
This mechanism is conceptually similar to an internal redirect.
It is useful, for example, when you want multiple components to share
a common error screen. Instead of having multiple view functions
check if the controller result indicates an error has occurred, you can
use the `replace' tuple to simply redirect the rendering to an error
component.
Controller functions may also return (nested) lists of
'ewc' and 'data' tuples, telling ErlyWeb to render the items
in their order of appearance and then send the list of their results to
the view function.
This feature lets you build components that are composed of a mix of
dynamic data and one or more sub-components.
If a component is only supposed to be used as a subcomponent, you should
implement the function "`private() -> true.'" in its controller.
This tells ErlyWeb to call `exit({illegal_request, Controller})'
when clients try to access the component by requesting
its corresponding URL directly.
=== Returning Arbitrary Yaws Tuples ===
ErlyWeb provides the infrastructure for rendering components and redirecting
to other components in the same application. This is sufficient for simple
applications, but sometimes you may want to return
to Yaws tuples that Yaws understands and that aren't directly
supported by ErlyWeb (these are documented
at [http://yaws.hyber.org/yman.yaws?page=yaws_api]). A common requirement,
for example, is to instruct Yaws to include arbitrary HTTP headers, such as
cookies, in the response Yaws sends to the browser.
ErlyWeb lets you do this by returning a tuple of the
form `{response, Elems}' from controller functions.
The second element in the `{response, Elems}' is a list of values that
ErlyWeb should return to Yaws verbatim, with the exception of the (optional)
`{body, Body}' for HTML or `{body, MimeType, Body}' tuple and any of the `ewr'
tuples listed above (ErlyWeb translates the latter into their `redirect_local'
equivalents). If, in addition to returning standard Yaws tuples, you want
ErlyWeb to render the response's body using the component's view, you can
include the `{body, Body}' or `{body, MimeType, Body}' tuple in `Elems'.
`Body' may be any single `ewc' or `data' tuple, or a list thereof.
ErlyWeb renders the elements of `Body', sends the result to the view function,
and embeds the resulting iolist in an `{html, Iolist}' tuple prior to
returning it to Yaws.
There is currently a restriction on the usage of `{response, Elems}': only the
top-level component for the request may return the
`{response, Elems}' tuple. Sub-components may return only `data' and/or
`ewc' tuples. This restriction makes sense because you normally only use
sub-components for rendering segments of the response's body and not
for setting HTTP headers or implementing arbitrary application logic.
=== Optional Controller Hooks ===
{@section catch_all/2}
{@section before_call/2}
{@section before_return/3}
{@section after_render/3}
==== catch_all/2 ====
The catch_all/2 hook was introduced in ErlyWeb 0.7. By default,
ErlyWeb treats the URL token following the component name
as a function name. catch_all/2 lets you override this behavior on a
per-controller basis by treating all URL tokens following the
component name as parameters.
For example, if a user requests the url
```
http://my-cool-app.com/people/bob
'''
ErlyWeb by default would try to invoke the function 'bob/1' in
people_controller. You can tell ErlyWeb to override this behavior and instead
pass "bob" as a parameter to people_controller:catch_all/2 as follows:
people_controller.erl:
```
-module(people_controller).
-export(index/1, catch_all/2).
catch_all(A, [Name]) ->
{data, Name}.
'''
people_view.et:
```
<%@ catch_all(Name) %>
<% Name %>'s homepage
...
'''
Note that the second parameter to catch_all/2 in the controller is a list
of URL tokens (separated by "/") following the controller name. For example,
when ErlyWeb receives a request with the URL
```
http://my-cool-app.com/people/bob/smith
'''
ErlyWeb would call `people_controller:catch_all(A, ["bob", "smith"])'.
==== before_call/2 ====
ErlyWeb lets you implement the before_call/2 and before_return/3 hooks
in controllers to intercept function calls before they are applied
and/or manipulate controller return values before ErlyWeb processes them.
The signature of before_call/2 is
```
before_call(FuncName::atom(), Params::[term()]) ->
{NewFuncName::atom(), NewParams::[term()]}
'''
You can use the before_call/2 hook to change the controller function ErlyWeb
calls and/pr the parameters ErlyWeb passes to the function.
This hook can be convenient, for example, for implementing authentication
logic for some or all functions of a controller.
==== before_return/3 ====
The signature of before_return/3 is
```
before_return(FuncName::atom(), Params::[term()], Response::term()) ->
NewResponse::term()
'''
By implementing the before_return/3 hook, you can change all return values
based on the names and/or parameters of their originating controller
functions. This hook can simplify the definition of response elements
such HTTP headers common to some or all of a controller's functions.
==== after_render/3 ====
ErlyWeb lets you implement the after_render/3 hook in controllers to get
access to the rendered output.
The signature of after_render/2 is
```
after_render(FuncName::atom(), Params::[term()], Rendered::iolist()) ->
Ignored::term().
'''
You can use the after_render/3 hook to implement a granular caching system.
== Views ==
Views are Erlang modules whose functions return iolists (nested lists
of strings and/or binaries). A view function that ErlyWeb uses has
the same name as its corresponding controller function,
and it accepts a single parameter, which is the result of ErlyWeb's
processing of the controller function's return value.
== Containers ==
Containers are ErlyWeb components in which other components can
be nested. ErlyWeb contains no special logic to enable containers;
'container' is just a word used to describe components that applications
use in a special way.
What sets containers apart from standard components is that containers'
controller functions accept as parameters
`ewc' and/or `data' tuples representing nested sub-components.
The controller functions include those parameters in their return values,
telling ErlyWeb to render the nested components and pass the results
to the container's corresponding view functions. The view functions embed
the rendered subcomponents in the container's static HTML,
and ErlyWeb returns the final result Yaws.
The code below illustrates how to implement a
simple HTML container. The container's controller function tells ErlyWeb
to renders its sub-component (index/2's `Ewc' parameter), and pass the result
to the component's view, which defines a basic HTML page.
html_container_controller.erl:
```
-module(html_container_controller).
-export([index/2, private/0]).
%% tells ErlyWeb to reject direct HTTP requests for this
%% container
private() ->
true.
index(_A, Ewc) ->
Ewc.
'''
html_container_view.et:
```
<%@ index(Data) %>