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>
</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" >&#1050;&#1072;&#1082; &#1074;&#1072;&#1089; &#1079;&#1086;&#1074;&#1091;&#1090;?</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,194