Step number 1
Introduction
In this step we will create a simple application for listing a shop database and displaying the data on the website.
Code for that application is available here.
Skills we will learn
- Accessing the controller
- Reading the records from the DB
- Using wpart_gen
- Using template engine
Tutorial
The first thing we have to do is to choose the address for our listing service.
In order to access the controller directly we will enter http://localhost:8080/app/shop/list into our browser.
This address will be exactly mapped to the call of shop:list function.
Every URL which path starts with app/ will be translated into the function call.
Because there is a rule which tells: "do not trust anyone, even your mother" we validate each call before the right function evaluation.
In our case the call to the shop:list will be preceded by call to the shop:validate, which takes only one argument: the name of the function we want to access (list).
Validate function must return either a tuple {error, Reason} (when something went bad and we don't want to allow user to access the right function) or {ok, Args} where Args is the list of the arguments we want to pass to our function.
Code responsible for that is in shop module:
-module(shop).
-export([validate/1]).
-export([list/0]).
validate(list) ->
{ok, []}.
list() ->
AllItems = e_db_mnesia:read(shop),
LimitedItems = lists:sublist(lists:reverse(AllItems), 5),
wpart:fset("items", LimitedItems),
{template, "list.html"}.
Our validate function doesn't check anything and returns an empty list ([]) that means the list function won't take any argument.
In our case validation always end with a success - so we enter the list function and prepare the environment for displaying the contents of our shop.
One of the first things to do in this step is to retrive data from the database.
Erlang Web provides a very useful API to Mnesia database, so getting the items from it is easy.
To read all the records from the database, we should call: e_db_mnesia:read(TableName) where TableName is the name of the table we want to access.
If we know the ID of particular entry, we can access it by calling: e_db_mnesia:read(TableName, Id).
Writing items to Mnesia is easy as well: e_db_mnesia:write(TableName, Item) where Item is a record (we assume its second element is an ID).
After loading all records from the Mnesia table, we are ready to narrow them and insert them into the request dictionary. To store wanted value under some key in request dictionary, we must use wpart:fset(Key, Value) function. Later, when we will be ready to access it - we simply call wpart:fget(Key) and get the stored value.
Once all those preparations are done we are almost ready to serve the content of the site, so we return the tuple: {template, "list.html"}.
That means we want to expand a template with the name list.html which is placed under templates/ directory of our project.
Let's check it out:
<wtpl:parent path="templates/base.html">
<wtpl:content name="content">
<ul>
<wpart:list select="map" list="items" as="item">
<li><wpart:shop /></li>
</wpart:list>
</ul>
</wtpl:content>
</wtpl:parent>
During the template expanding Xmerl (Erlang XML parser) iterates over every tag and when the first tag in the file it encounters is wtpl:parent - starts the template engine.
Template engine loads the file specified by attribute path (in our case: templates/base.html), and looks inside of it for slots it can fill.
Each slot is specified by name attribute: the declaration of the "empty" hole is <wtpl:include name=Name /> and the declaration of the HTML brick we want to insert into that hole: <wtpl:content name=Name />.
So having only one empty slot (with the name content - declared in base.html) and one filling (declared in list.html) template engine substitutes the wtpl:include tag with inside of the wtpl:content of the same name.
After all, as a result of template engine work we will get:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Erlang Web Shop Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>Erlang Web Shop</h1>
<ul>
<wpart:list select="map" list="items" as="item">
<li><wpart:shop /></li>
</wpart:list>
</ul>
</body>
</html>
Unfortunately we can't stop here. There are two more tag which need expanding: wpart:list and wpart:shop.
First of them is a part of framework. What it does is it takes the key to the list (list attribute) of arguments stored in request dictionary and manipulates on them.
In this case we have "map" type of manipulation, what means we will evaluate the body of the tag for each element of the arguments list and display it on the page.
To access the elements inside the body, we need some "handle": it is specified by as attribute.
So, to sum up, we will evaluate the content of the tag for all elements inside the list stored under items key in the request dictionary.
The current element will be available under item key in the request dictionary.
Other useful features of wpart:list wpart are described in the manual.
The second tag is user-defined one.
So if Xmerl encounters a wpart:name tag during its template expanding - it will call wpart_name:handle_call function which takes only one argument: the XML structure of the tag.
Handle_call should return either a record #xmlText or #xmlElement with value which should be displayed instead of the wpart tag.
Here is our part of code:
-module(wpart_shop).
-export([handle_call/1]).
-include_lib("xmerl/include/xmerl.hrl").
-include("shop_item.hrl").
handle_call(_XML) ->
Item = wpart:fget("item"),
#xmlText{value = create_item_desc(Item),
type = cdata}.
Here is the place where we are looking for the previously set (during the iteration) value - wpart:fget("item"). Next we build the content we want to display (create_item_desc(Item)) and return it to the browser.
In order to avoid building HTML code inside Erlang modules, Erlang Web is using wpart_gen tool for creating and building the small HTML snippets.
The idea is to keep the standard skeletons outside of the code and inject only the changeable elements.
For example, the snippet of our link will look like:
<a href="<% here place the URL %>"><% here place the title of the link %></a>
Before using those small snippets, we have to prepare and load them.
Preparing is very simple: we just have to create a separete file for each little template (good convention is to save them with .tpl extension under one direcotry). The place we want to insert our case specific content is indicated by <% slot %> tag.
Our previous example (with a tag) will look like:
<a href="<% slot %>"><% slot %></a>
The second thing is loading the templates.
Because during the development of the system the number of the snippets is increasing, we should group our own in namespaces.
Function responsible for loading templates is wpart_gen:load_tpl(Namespace, Name, Path). The tuple {Namespace, Name} identifies the template in the systen, so we can hold both {shop, a} and {cart, a}. Path parameter specifies the path to the .tpl file.
After the start of the system we should call the loaders one-by-one, but we can also create some kind of starter module:
start() ->
Tpls = [{li, "templates/tpls/li.tpl"},
{ul, "templates/tpls/ul.tpl"},
{item_short, "templates/tpls/item_short.tpl"}],
lists:foreach(fun({Name, Path}) ->
wpart_gen:load_tpl(shop, Name, Path)
end, Tpls).
This part of code will load the three templates (with the keys {shop, li}, {shop, ul} and {shop, item_short}).
Having templates in memory allows us to use them inside of our modules. Here wpart_gen lends us a helping hand too. To access the selected template we have to pass the namespace and the name of the loaded template: wpart_gen:tpl_get(Namespace, Name). Then we should fill all the slots with the case specific content: wpart_gen:build_html(Tpl, Content) where Tpl is a loaded template returned from tpl_get and Content is a list of the elements we want to insert into the snippet (as we can notice, length(Content) should equal the number of the slots in the template file).
If we know how to perform the basic operations on model and view, we can finally build a controller. During the expanding of the wpart_shop tag we used a create_item_desc function. Let's check what is inside it:
create_item_desc(Item, Acc) ->
Skeleton = wpart_gen:tpl_get(shop, item_short),
wpart_gen:build_html(Skeleton, [Item#item.title,
integer_to_list(Item#item.quantity),
integer_to_list(Item#item.prize)]).
At the beginning we are getting the template for item_short tag. Having it allows us to build an item description and at the end return it back to the XML expander.
It is ready now - we can compile the example (inside the step root directory):
erl -make
launch yaws server:
erl -pa lib/*/ebin -yaws embedded true -s e_mod_yaws
If we are running it for the first time, in the Erlang shell we should run:
starter:db_start().
starter:start().
In another case (where we have created the Mnesia schema and tables) simple
starter:start().
will be enough.
Our example website is accessible under http://localhost:8080/app/shop/list URL.
