FROM: 5HT
TO: #N2O
DATE: 3 MAR 2015

N2O Application Server

REPL

After downloading and bootstraping a sample shipped with N2O you should be able to see colored repl. This is regular Erlang shell, but it is aware of OTP applications placed in apps and deps directories, how to resolve them and perform proper starting.

Single Page Application

The page is usually contains HTML controls upon which event handlers will binds. Also you should specify a list of n2o.js modules, like protocols, components, translations or other modules with up to date client functionality. Here is sample SPA:

During each initial route connection, main/0 function followed by event(init) is being called to render postbacks of active controls. Same page module could serve SPA and/or DTL pages. N2O_start() boots up a supported connection and send initial <<"N2O,">> authorization packet to open a protocol stream connection.

Listing 1. Route module

-module(index).
-compile(export_all).
-include_lib("n2o/include/wf.hrl").

main()    -> #dtl { file="index",
                    app=sample,
                    bindings=[{body,body()} ] }.

body()    -> [ #panel{id=history},
               #textbox{id=message},
               #button{id=send,body="Chat",
                       postback=chat,source=[message]} ].

event(init) -> wf:info(?MODULE,"init~n",[]);
event(E)    -> wf:info(?MODULE,"event ~p~n",[E]).

After establishing WebSocket connection you should be able to see IO answer terminated in JSON encoding, e.g. {eval:"console.log('ok');",data:''}. Here is the IO answer generated for 'send' button postback:

Listing 2. Test WebSocket connection

$ tcpkali -T1s --verbose 3 \
     --first-message "N2O," --ws 127.0.0.1:8000/ws/

Destination: [127.0.0.1]:8000
Data(161): ➧HTTP/1.1 101 Switching Protocols\r\n
  connection: Upgrade\r\n
  Access-Control-Allow-Origin: *\r\n
  upgrade: websocket\r\n
  sec-websocket-accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\r\n
  \r\n⬅︎
Ramped up to 1 connections.
Data(414): ➧\201~\001\232{"eval":"{ var x = qi('send'); 
x && x.addEventListener('click',function (event){{ if 
(validateSources(['message'])) ws.send(enc(tuple(atom(
'pickle'),bin('send'),bin('g2gCaAVkAAJldmQABWluZGV4ZAA
EY2hhdGsABHNlbmRkAAVldmVudGgDYgAABZFiAAVgSmIABt6n'),
[tuple(atom('message'),querySource('message')),tuple(
tuple(utf8_toByteArray('send'), bin('detail')), event.
detail)])));else console.log('Validation Error'); }});
};"}
Total data sent:     166 bytes (166 bytes)
Total data received: 575 bytes (575 bytes)
Bandwidth per channel: 0.006 Mbps, 0.7 kBps
Aggregate bandwidth: 0.005↓, 0.001↑ Mbps
Test duration: 1.00634 s.

Web Logic

Web logic shoud be as much declarative as possible at the same time the web logic language should be readable by Java, .NET engineers. Web Logic of Deposits Application is powered by N2O WebSocket Application Server.

Note: Operating clean and minimalistic API will allow you engineers to think on core features of the system, SpawnProc provides most minimal Business Processing frameworks and libraries.

Autogenerated Forms

You have total freedom in developing your web form applications: using low-level nitrogen DSL erlang records markup; or you may use declarative forms definition, using nitrogen DSL as target language. There are other options, like non-HTML control elements with spa protocol or DTL or other template enginer rendering.

Picture 1. One-Time Password Form

Listing 3. OTP Form

document(Name,Phone) -> #document { name = Name,

    sections = [   #sec { name= [ deposits:translate({?MODULE,message}), 
                          wf:to_list(Phone#phone.number)] } ],
    buttons  = [   #but { name='decline', title=deposits:translate(decline),
                          class=cancel, postback={'CloseOpenedForm', Name} },
                   #but { name='next', title = deposits:translate(proceed),
                         class = [button,sgreen], sources = [otp],
                         postback = {'Spinner', {'OpenForm',Name}} } ],
    fields   = [ #field { name='otp', type=otp, 
                          title= deposits:translate({?MODULE,pass}),
                          labelClass=label, fieldClass=column3} ] }.

Picture 2. Open Application Form

Listing 4. Open Application Form

document(Name,Curr) ->
                #document { name = Name,
    sections = [    #sec  { name= deposits:translate(name) } ],
    buttons  = [    #but {  name='decline', title = deposits:translate(decline),
                            class=cancel, postback={'CloseOpenedForm', Name} },
                    #but {  name='next', title = deposits:translate(proceed),
                            class=[button,sgreen],
                            postback={'Spinner', {'CheckApp',Name}},
                            sources= [depositName,money,getMoneyCard,
                                     getMoneyCash,cardSelectFrom,
                            capitalization,cardSelectTo,
                            partialWithdrawal,codeValue] } ],
    fields   = [   #field { name='duration', type=integer,
                            title= deposits:translate(duration),
                            pos = #deposit_app.duration,
                            format = deposits:translate(durationformat),
                            tooltips = [deposits:translate(tooltip1)] },
                   #field { name='rate', type=integer,
                            title= deposits:translate(rate),
                            pos = #deposit_app.rate,
                            format = deposits:translate(rateformat),
                            postfun = fun(X) -> proplists:get_value(rate,X) end,
                            tooltips = [deposits:translate(tooltip2)]},
                   #field { name=money, type=money, curr= Curr,
                            min=20, max=100000,
                            title= deposits:translate(ammount),
                            pos = #deposit_app.amount,
                            tooltips = [deposits:translate(tooltip3) ]},
                    #field { name='depositName', type=string,
                             length=30,min=0, max=30,
                            title= deposits:translate(title),
                            pos = #deposit_app.name,
                            tooltips = [deposits:translate(tooltip4)] },
                    #field { name='bonusCode', type=combo,
                            title= deposits:translate(bonus),
                            options= [
                               #opt{name='noCode',checked=true,
                                    title = deposits:translate(no)},
                               #opt{name='bonusCode'
                                    ,title = deposits:translate(yes)}],
                             tooltips = [deposits:translate({?MODULE, tooltip5})] },
                    #field { name='codeInput', type=empty },
                    #field { name='charge', type=combo,
                            title= deposits:translate(source),
                            options= [
                                 #opt{name='getMoneyCash',
                                      title = deposits:translate(cash)},
                                 #opt{name='getMoneyCard',checked=true,
                                      title = deposits:translate(card)}],
                            tooltips = [deposits:translate(tooltip7),
                                        deposits:translate(tooltip6)] },
                    #field { name=[cardsFrom,userCardsFrom,cardSelectFrom],
                             type=card,
                            title= deposits:translate(yourcard) },
                    #field  { name='percents', type=combo,
                            title= deposits:translate(action),
                            options= [
                                #opt{name='capitalization',checked=true,
                                     title = deposits:translate(add)},
                                #opt{name='partialWithdrawal',
                                     title = deposits:translate(move)}],
                            tooltips = [deposits:translate({?MODULE, tooltip9}),
                                        deposits:translate(tooltip8)] },
                    #field { name=cardsPercent, type=empty } ] }.


Rendered DOM javascript command, Bytes:
> wf:actions(undefined).
> wf:update("1",open_app:new('DP00_12_uah')).
> size(iolist_to_binary(wf:render(wf:actions()))).
4935

Compressed DSL form in BERT, Bytes:
> size(term_to_binary(open_app:new('DP00_12_uah'),[compressed])).
2347
> size(term_to_binary(open_app:new('DP00_12_uah'))).
56474

Declarative compressed FORM in BERT, Bytes:
> size(term_to_binary(open_app:document(maxim,"uah",y,y,y))).
3961
> size(term_to_binary(open_app:document(maxim,"uah",y,y,y),
                                              [compressed])).
1286