Nitrogen
Erlang Web Framework
WHY ERLANG?
-
Functional
-
Distributed
-
Fault-tolerant
- Process-oriented
WHY NOT ERLANG?
-
Weird syntax
-
Computational performance
- Stings handling (unicode)
- Libraries
ERLANG WEB SERVERS
- Inets
- YAWS
- Cowboy
- MochiWeb
- WebMachine
ERLANG WEB FRAMEWORKS
- Nitrogen
- N2O
- ChicagoBoss
- Zotonic
NITROGEN
- Client/Server messages
- Maps URI to Erlang modules
- DSL for HTML
- Records
NITROGEN TERMS
- Page
- Element
- Action
- Postback
- Handler
BARE TEMPLATE
<html>
<title>[[[page:title()]]]</title>
<script src='/nitrogen/jquery.js'></script>
<script src='/nitrogen/jquery-ui.min.js' ></script>
<script src='/nitrogen/livevalidation.min.js'></script>
<script src='/nitrogen/nitrogen.min.js'></script>
<body>
<div class="container">[[[page:body()]]]</div>
<script>[[[script]]]</script>
<script>[[[script]]]</script>
</body>
</html>
QUICK START
Pull the Nitrogen Source Code
$ make rel_inets
$ cd ../myapp
bin/nitrogen console
INDEX.ERL
-module (index).
-compile(export_all).
-include_lib("nitrogen_core/include/wf.hrl").
-include("records.hrl").
main() ->
#template { file="./site/templates/bare.html" }.
body -> "".
BODY FUNCTION
body() ->
[
#panel { class="form-group", body = [
#textbox { id=search, class="form-control" }
] }
#button { class="btn", text = "Search",
actions=#event{type=click,postback=search} },
#panel { id=result }
].
EVENT HANDLERs
event(search) ->
Name = util:q(search),
case query_names(Name) of
{ok, _, []} ->
wf:update(result, #panel {
id=result, body=new_node(Name) });
{ok, _, Results} ->
wf:update(result, #panel {
id=result, body=format_names(Results) })
end;
DATABASE
query_names(Name) ->
db:equery("SELECT node_id, name FROM name WHERE name LIKE $1 ORDER BY name DESC", [Name]).
format_names(Names) ->
[ #link { text=Name, url=io_lib:format("node/view/~B", [Id]) } || {Id, Name} <- Names ].
new_node(Name) -> [
#panel { text=wf:f("No point \"~ts\".", [Name]) },
#link { text="Create?", actions=#event{type=click,postback={create,Name}} } ].
ACTIONS
body() ->
wf:wire(mybutton, #effect { effect=pulsate }),
[
#button { id=mybutton, text="Submit" }
].
POSTBACK
body() ->
wf:wire(mybutton, #event { type=click, postback=myevent }),
[
#button { id=mybutton, text="Submit" }
].
event(myevent) ->
?PRINT({event, now()}).
COMET/WEBSOCKET
body() ->
wf:comet(fun() -> counter(1) end),
#panel { id=placeholder }.
counter(Count) ->
timer:sleep(1000),
wf:update(placeholder, integer_to_list(Count)),
wf:flush(),
counter(Count + 1).
AUTO SYNC
setup_sync() ->
sync:go(),
{ok, Comet} = wf:comet(fun() -> reload() end),
sync:onsync(fun(_) -> Comet ! {self(), reload} end).
reload() ->
receive
{_, reload} -> wf:wire( "location.reload()" ), wf:flush()
end.
DATABASE
- Connection Pool
- Database drivers
- No DBI
DB APPLICATION
init([]) -> ...
squery(Sql) ->
poolboy:transaction(pool1, fun(Worker) ->
gen_server:call(Worker, {squery, Sql})
end).
equery(Stmt, Params) ->
poolboy:transaction(pool1, fun(Worker) ->
gen_server:call(Worker, {equery, Stmt, Params})
end).
DB WORKER
init(Args) ->
process_flag(trap_exit, true),
Host = proplists:get_value(hostname, Args), ...
{ok, Conn} = epgsql:connect(Host, ... , [{database, DB}]),
{ok, #state{conn=Conn}}.
handle_call({squery, Sql}, _From, #state{conn=Conn}=State) -> {reply, epgsql:squery(Conn, Sql), State};
handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) -> {reply, epgsql:equery(Conn, Stmt, Params), State};
handle_call(_Request, _From, State) -> {reply, ok, State}.
STRINGS
body() -> [
#label { text = <<"Как вас зовут?"/utf8>> }
].
<label class="wfid_temp586286 nitrogen_label" >Как вас зовут?</label>
STRINGS
body() -> [
#label { text = "Как вас зовут?" }
].
<label class="wfid_temp586286 nitrogen_label" >Как вас зовут?</label>
DB STRINGS
1. Plain English:
Str = "Hello", epgsql:equery(Conn, "INSERT into name (name) VALUES ($1)", [Str]).
{ok, 1}.
DB STRINGS
2. Plain unicode:
Str = "привет", epgsql:equery(Conn, "INSERT into name (name) VALUES ($1)", [Str]).
** Reason for termination ==
** {badarg,[{erlang,list_to_binary,[[1087,1088,1080,1074,1077,1090]],[]},
{epgsql_binary,encode,3,
[{file,"src/epgsql_binary.erl"},{line,80}]},
DB STRINGS FIX
Str = [1087,1088,1080,1074,1077,1090],
StrUni = unicode:characters_to_binary(Str, unicode, utf8),
epgsql:equery(Conn, "INSERT into name (name) VALUES ($1)", [StrUni]).
DB TYPES
{ok, _, [{_Id}]} = db:squery("select nextval('node_id_seq')").
{ok, <<"14">>}.
db:equery("INSERT INTO name (node_id, name) VALUES ($1,$2)", [_Id, Name]).
** Reason for termination ==
** {{badarg,[{epgsql_binary,encode,3,
[{file,"src/epgsql_binary.erl"},{line,57}]},
Id = binary_to_integer(_Id).
CUSTOM ACTIONS
-module (element_fg).
-include_lib("nitrogen_core/include/wf.hrl").
-include("records.hrl").
render_element(E = #fg{}) ->
LabelId = wf:temp_id(),
#panel{class="form-group",body=[
io_lib:format("<label for=\"~s\">~s</label>",
[LabelId, E#fg.label]),
#textbox{html_id=LabelId,
text=E#fg.value, id=E#fg.id, class="form-control"}
]}.
RECORDS
-include("plugins.hrl").
-record(dialog,
{?ELEMENT_BASE(element_dialog), body=[],buttons=[]}).
-record(dialog_close, {?ACTION_BASE(action_dialog)}).
-record(dialog_show, {?ACTION_BASE(action_dialog)}).
-record(fg, {?ELEMENT_BASE(element_fg), label="", placeholder="", value=""}).
ACTIONS
-module (action_dialog).
-include_lib ("nitrogen_core/include/wf.hrl").
-include("records.hrl").
-export([render_action/1]).
render_action(#dialog_close{target = Target}) -> wf:f("console.log('close: ~s');jQuery(obj('~s')).remove();", [Target, Target]);
render_action(#dialog_show{target = Target}) -> wf:f("console.log('show:' + '~s' + ' : ' + obj('~s'));jQuery(obj('~s')).modal();", [Target, Target, Target]).
FUTURE
- ErlyDTL
- BERT
- Shen
- Reactive
Nitrogen
By Roman Galeev
Nitrogen
Nitrogen hands-on.
- 3,166