Wednesday, August 24, 2011

Nitrogen, an Erlang web application library/framework Part 10


For those just now encountering this series, you'll want to start with Part 1, and at least have Introduction to Nitrogen open in another tab.


Slide 80: Extending Nitrogen: Handlers - Part 3 - continued


I floundered around in the code until I found a mention of the logging that Nitrogen does. Logging! The first thing I should have looked at. There, in /home/sps/erlang-libs/nitrogen/rel/nitrogen/log/sasl-error.log I found this:

exception throw: {must_define_a_nitrogen_behaviour,my_security_handler}
in function wf_handler:set_handler/2
in call from nitrogen_mochiweb:loop/1
in call from mochiweb_http:headers/5

But I did define a nitrogen behavior! Except, apparently, Erlang shows its european heritage by using the British spelling of behavioUr. Fix that. Compile. Run. Behave as expected!

The End


That's the end of the tutorial, and thus the end of this series. I'll start a new series when I encounter a new tutorial to study thoroughly.

Sunday, August 7, 2011

Nitrogen, an Erlang web application library/framework Part 9


For those just now encountering this series, you'll want to start with Part 1, and at least have Introduction to Nitrogen open in another tab.


Slide 78: Extending Nitrogen: Handlers - Part 1


Ok, good, you can override nearly anything you might care about.


Slide 79: Extending Nitrogen: Handlers - Part 2


Do not attempt to override something in a stage you haven't reached yet. Pages go through ten stages - but how do you specify where in the process your code executes?


Slide 80: Extending Nitrogen: Handlers - Part 3


Answer: you specify a behavior corresponding to the stage of processing you're affecting, and write functions appropriate to that behavior. It's not clear where the new code is supposed to live. Is there other code that appears similar to my_security handler?

bash-4.1$ pwd
/home/sps/erlang-libs/nitrogen/rel/nitrogen/site
bash-4.1$ find . -name '*security*' -print
bash-4.1$ cd ..
bash-4.1$ find . -name '*security*' -print
bash-4.1$ cd ..
bash-4.1$ find . -name '*security*' -print
bash-4.1$ cd ..
bash-4.1$ find . -name '*security*' -print
./Quickstart/src/demos/demos_security_restricted.erl
./Quickstart/src/demos/demos_security_login.erl
./Quickstart/src/demos/demos_security.erl
./apps/nitrogen/src/handlers/security
./apps/nitrogen/src/handlers/security/http_basic_auth_security_handler.erl
./apps/nitrogen/src/handlers/security/security_handler.erl
./apps/nitrogen/src/handlers/security/default_security_handler.erl
./apps/nitrogen/ebin/default_security_handler.beam
./apps/nitrogen/ebin/security_handler.beam
./apps/nitrogen/ebin/http_basic_auth_security_handler.beam
bash-4.1$ pwd
/home/sps/erlang-libs/nitrogen

Hmm. I don't want to plunk it down with the other canonical source. When we added a new action, it went in site/src/actions. Let's put our handler in site/src/handlers.


Slide 80: Extending Nitrogen: Handlers - Part 3 (more)


Um, nitrogen_inets.erl. That's located...
bash-4.1$ pwd
/home/sps/erlang-libs/nitrogen/rel/nitrogen/site
bash-4.1$ find . -name nitrogen_inets.erl -print
bash-4.1$ cd ..; find . -name nitrogen_inets.erl -print
bash-4.1$ cd ..; find . -name nitrogen_inets.erl -print
./overlay_inets/site/src/nitrogen_inets.erl
bash-4.1$

Ok, I think that was created when we created the site. I'm going to guess that a new file in our src directory will get combined with the existing code in some useful way, so I'm going to put this in /home/sps/erlang-libs/nitrogen/rel/nitrogen/site. Looking at the other nitrogen_inets.erl file, we see that there are (of course) -module and -export statements in the file as well. Also, looking at that file, it appears we're replacing its function instead of augmenting it.

Let's fire up nitrogen again:



bash-4.1$ cd bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
bash-4.1$ ls
dev nitrogen
bash-4.1$ ./nitrogen console
Exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/erts-5.8.2/bin/erlexec -boot /home/sps/erlang-libs/nitrogen/rel/nitrogen/releases/2.0.4/nitrogen -embedded -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/app.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/mochiweb.config -args_file /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/vm.args -- console
Root: /home/sps/erlang-libs/nitrogen/rel/nitrogen
Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:5] [kernel-poll:true]

Eshell V5.8.2 (abort with ^G)
(nitrogen@127.0.0.1)1> Starting Mochiweb Server (nitrogen) on 0.0.0.0:8000, root: './site/static'

...and in another window

bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/nitrogen_inets
Recompile: ./src/handlers/my_security_handler
:: MAKE - ./site
:: Done!
bash-4.1$

And now, did all that work? Pointing the web browser at http://localhost:8000, we get prompted for a login.

Not behaving as expected: I can still get to http://localhost:8000/chat, which, not being prefixed by "my_", shouldn't work.


WHOOPS!


At some point I must have played with using the mochiweb server instead of inets, and that means nitrogen_inets.erl is getting ignored. Let's check the git log to see if I can back up to where I switched.


Way, way back at the 4th screen! Which I didn't even think was worth mentioning. Sheesh. Ok, so now I've bought myself some, uh, experience, and I'll need to come up with nitrogen_mochiweb.erl instead of nitrogen_inets.erl. Let's see if I can pull that off. How about doing this:



bash-4.1$ git diff
diff --git a/rel/nitrogen/site/src/nitrogen_mochiweb.erl b/rel/nitrogen/site/src
index 0ee336a..f2c7dd0 100644
--- a/rel/nitrogen/site/src/nitrogen_mochiweb.erl
+++ b/rel/nitrogen/site/src/nitrogen_mochiweb.erl
@@ -25,4 +25,5 @@ loop(Req) ->
RequestBridge = simple_bridge:make_request(mochiweb_request_bridge, {Req, D
ResponseBridge = simple_bridge:make_response(mochiweb_response_bridge, {Req
nitrogen:init_request(RequestBridge, ResponseBridge),
+ nitrogen:handler(my_security_handler, []),
nitrogen:run().

Stop the nitrogen server, compile that, then start nitrogen again. OK, the index now gives a blank page, probably because it isn't prefixed with "my_". But http://localhost:8000/my/page doesn't work either. I'm going to need a deeper dive into the code to make this work.

Monday, June 27, 2011

Nitrogen, an Erlang web application library/framework Part 8


For those just now encountering this series, you'll want to start with Part 1, and at least have Introduction to Nitrogen open in another tab.


Slide 75: Extending Nitrogen: Custom Actions - Part 1


As described in the tutorial, create the file for the action, with its dummied code.


bash-4.1$ ./dev action my_action
Created action: ./site/src/actions/action_my_action.erl
Remember to recompile!
bash-4.1$

Slide 76: Extending Nitrogen: Custom Actions - Part 2



OK, a record and one mandatory function. Again following the suggestion of the comment, I move the record to ${Root}/include/records.hrl. I also change the dummy code in render_action/1 to match the tutorial example. Not much to typo there, but let's see...



Nonetheless, I manage to make one: the dummy function in action_my_action.erl has an underscore prefixed to the matching variable _Record, but since we actually use that variable in the function body, it must not be anonymous (as an underscore declares it). Fix that, and I get a clean compile:


bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/my_page
Recompile: ./src/login
Recompile: ./src/chat
Recompile: ./src/elements/element_my_element
Recompile: ./src/actions/action_my_action
:: MAKE - ./site
:: Done!

Slide 77: Extending Nitrogen: Custom Actions - Part 3



Now to use it in my_page.erl. Yup, that worked, after I removed an extraneous -> I'd introduced (what was I thinking?).

Tuesday, March 29, 2011

Nitrogen, an Erlang web application library/framework Part 7

Slide 71: Extending Nitrogen: Custom Elements - Part 1


Picking up again, ./dev element my_element won't run unless nitrogen is running. So in one window:

bash-4.1$ ./dev element my_element
Node is not running!
bash-4.1$ ./nitrogen console
Exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/erts-5.8.2/bin/erlexec -boot /home/sps/erlang-libs/nitrogen/rel/nitrogen/releases/2.0.4/nitrogen -embedded -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/app.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/mochiweb.config -args_file /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/vm.args -- console
Root: /home/sps/erlang-libs/nitrogen/rel/nitrogen
Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:5] [kernel-poll:true]

Eshell V5.8.2 (abort with ^G)
(nitrogen@127.0.0.1)1> Starting Mochiweb Server (nitrogen) on 0.0.0.0:8000, root: './site/static'

...and in the other window:

bash-4.1$ ./dev element my_element
Created element: ./site/src/elements/element_my_element.erl
Remember to recompile!
bash-4.1$

Slide 72: Extending Nitrogen: Custom Elements - Part 2



OK, record and two mandatory functions.

Slide 73: Extending Nitrogen: Custom Elements - Part 3



Hmmmm. The tutorial presents only one of the two mandatory functions, and no record. Let's see how creative my typos are this time.



Ah, the missing pieces are dummied in the ${Root}/src/elements/element_my_element.erl file, along with an instruction to move the record definition to the ${Root}/include/records.hrl. I'll leave them be, and alter only the render_element function.


Slide 74: Extending Nitrogen: Custom Elements - Part 4



After replacing the contents of my_page.erl following the title/0 definition with the recommended code, and recompiling (fixing variable name typos until getting a clean compile),
the page http://localhost:8000/my/page works as expected.



OK; so if you have a number of form elements that repeatedly occur together, then you can bundle them up with this kind of "custom" element. It's not yet clear to me if this would be useful for an entirely new kind of element, e.g., a "treebutton" which would toggle between expanded and collapsed representation of a tree.

Saturday, January 22, 2011

Nitrogen, an Erlang web application library/framework Part 6

Slide 60: Overview of Nitrogen Validation (#is_required{})



wf:wire/3 associates the validation with its first argument, here a specific element labeled "submit", and specifies the validation target according to element label. The wf:wire/3 calls occur before the definition of the elements.

Slide 61: Overview of Nitrogen Validation (#custom{} and #js_custom{})


#custom{} for the server, #js_custom{} for the client. My experience in this exercise is an uninteresting series of typos, syntax errors, and misremembered names. The biggest take away is that Internal server error usually means I've called an undefined function - specifically, misremembered the name of a system function. To reorient myself after my absence, here's the full login.erl code:

%% -*- mode: nitrogen -*-
-module (login).
-compile(export_all).
-include_lib("nitrogen/include/wf.hrl").
-include("records.hrl").

main() -> #template { file="./site/templates/bare.html" }.

title() -> "Hello from login.erl!".

body() ->
wf:wire(submit, username, #validate { validators = [
#is_required { text = "Required" }
]}),
wf:wire(submit, password, #validate { validators = [
#is_required { text = "Required" },
#custom {
text = "Invalid password.",
function = fun(_, Value) -> Value == "password" end
}
]}),
#panel { style="margin: 50px;", body=[
#flash {},
#label { text="Username" },
#textbox { id=username, next=password },
#br {},
#label { text="Password" },
#password { id=password, next=submit },
#br {},
#button { text="Login", id=submit, postback=login }
]}.

event(login) ->
wf:role(managers, true),
wf:redirect_from_login("/").


Slide 66: A Comet Counter



How boring: I typed it correctly, and it worked as described :-).


Slide 67: Comet Pools



Functions wf:comet/2 and wf:send/2. Vague: does a function have to execute a receive to receive these messages, or does the message appear as the argument to the function?


Slide 68: Comet Pool Scope



Normally, scoped by page and user, with a refresh replacing old scope with new one. With wf:comet_global/2 and wf:send_global/2, they live for the life of the server.


Slide 69: The Simplest Chatroom Ever Constructed



Answer revealed! Functions do have to invoke receive to get pool messages. To observe the semi-obvious: the nitrogen console process must be in the foreground (e.g., running and attached to the terminal) for dev page newpage to function correctly.

Saturday, January 15, 2011

Nitrogen, an Erlang web application library/framework Part 5

Slide 48: Session State


I"m going to do minor variant on the exercise: the incrementing counter will belong to the session, the doubling counter will belong to the page. That way we're a few clicks further away from overflow.... Here's my result:

body() ->
#panel { style="margin: 50px", body=[
#button { id=mybutton, text="Submit", postback=click },
#panel { id=placeholder1, body=wf:f("~B", [wf:session_default(click_counter, 0)]) },
#panel { id=placeholder2, body="1" }
]}.

event(click) ->
Counter = wf:session_default(click_counter, 0) + 1,
wf:update(placeholder1, wf:f("~B", [Counter])),
wf:session(click_counter, Counter),
Doubler = wf:state_default(click_doubler, 1) * 2,
wf:update(placeholder2, wf:f("~B", [Doubler])),
wf:state(click_doubler, Doubler).

Slide 49: Their answer


Note: second argument to wf:update/2 is a string; I originally surrounded it with a #panel (i.e., div element) so I could say body=, but that was unnecessary, so the result above incorporates Slide 49's simplified answer (although I'm using the helper function wf:f because it's less to type than io_lib:format).

Slide 50: Security


Slide 51: Limiting Access to a Page (roles)


Slide 52: Authentication and Authorization Functions


wf:user/0 and wf:user/1 seem straightforward. wf:role/1 and wf:role/2 are opaque to me. Hypothesis: wf:role(Role) returns true/false whether the session has that role; wf:role(Role, IsInRole) uses IsInRole false to revoke that role from the session or IsInRole true to add that role to the session. It looks liek they're going to make me read the manual.

Slide 53: Page Redirection functions


wf:redirect/1

wf:redirect_to_login/1

wf:redirect_from_login/1

There is apparently something special about the LoginUrl.

Slde 54: Creating a Secure Page - Step 1


They don't give the name of this page. I'm going to name it managers.erl.

Note! The main/0 function has to this point done nothing but specify the html template. This code is orthogonal to that for title, body, and events.


bash-4.1$ ./dev page managers
Created page: ./site/src/managers.erl
Remember to recompile!

Slide 55: Creating a Secure Page - Step 2



bash-4.1$ ./dev page login
Created page: ./site/src/login.erl
Remember to recompile!

First - hey, I guessed correctly as to how wf:role works! Second, since the example redirects a successful login to /, I infer that the main/0 function that was supposed to have the authorization guard was index.erl. I'll go fix that page.

I'm getting an error on the login page, somewhere in the click event handler. My broken code:

body() ->
#button { text="Login", postback=login }.

event(click) ->
wf:role(managers, true),
wf:redirect_from_login("/").

Let's review: Events have types. Types have triggers. Triggers are associated with elements. The element in this case is a button which has an implicit type of "click". The associated trigger is named "login". In the event/1 definition, I need to specify the trigger, not the type. So replace event(click) with event(login). That worked.

Slide 56: Creating a Secure Page - Step 3


When I first tried the example, it let me right in. Roles are associated with sessions, and I'd already acquired the "managers" role with the previous example. Let's kill off the Nitrogen server and start it back up. Yup, that was it. "flash{}" wasn't the obnoxious tag I was afraid of :-).

Slide 57: Creating a Secure Page - Step 4


Ok. Where to put a "logout" button? index.erl and my_page; not login since you don't get there unless you're not logged in. index.erl looks like this:

body() ->
#container_12 { body=[
#grid_8 { alpha=true, prefix=2, suffix=2, omega=true, body=inner_body() }
]}.

inner_body() ->
[
#h1 { text="Welcome to My Nitrogen Tutorial" },
#p{},
"
If you can see this page, then your Nitrogen server is up and
running. Click the button below to test postbacks.
",
#p{},
#button { id=button, text="Click me!", postback=click },
#p{},
"
Run ./bin/dev help to see some useful developer commands.
",
#p{},
#button { text="Logout", postback=logout }
].

event(logout) ->
wf:logout(),
wf:redirect("/");
event(click) ->
wf:replace(button, #panel {
body="You clicked the button!",
actions=#effect { effect=highlight }
}).

HOWEVER, a logout button is something you'd want on every page. Is there a way to do that with the template "bare.html"?

The page template might interpolate [[[logout:logout_body]]] to put the logout button on the page, but how do we augment every event/1 function with the event(logout) action?

Friday, January 14, 2011

Nitrogen, an Erlang web application library/framework Part 4

Slide 40: More Event Examples


"mouseout" is distinguished only as being the last of the three event types to be mentioned within the mybutton list. Let's see what happens when we exchange that line with the line for the "click" type.

Aha; now only the click type has a function associated with it. Those "#event" elements look as if they're contained in a list. List elements are normally separated by a comma (","). Let's try putting a comma at the end of the first two "#event" lines.

Yes! Now we get all three events observed, and the page is working as intended.

Slide 41: More Event Examples


Key takeaway: wf:q(ElementID) lets you process data returned to the Nitrogen server.

Slide 42: Modifying Elements


A few things to note: to make it easier to see what was being replaced, I gave the initial panel some contents like so:

body() ->
#panel { style="margin: 50px", body=[
#button { id=mybutton, text="Submit", postback=click },
#panel { id=placeholder, body=[ #p { body="Text to replace" } ] }
]}.

Another thing: the "click" event can be triggered multiple times; when it does so, the "placeholder" element gets replaced each time - you see the time get changed.

Slide 46: Page State


So: the "body" function generates the page, the "event" functions dictates what the page, in cooperation with the Nitrogen server, will do (which may include changing the page). In this exercise, we want to have a counter start at some value (let's use 0), increment in response to a "click" type event on a "Submit" element, and everything should start over on a page reload. wf:state_default/2 needs to be called early. I can think of two alternatives: during body generation (so in the body/0 function), or in an "onpageload" event (I think that's the name; I'll need to look it up). I'll try using body/0 first.

Here's the first try, which fails when initially trying to generate the page body. The code:

body() ->
wf:state_default(click_counter, 0),
#panel { style="margin: 50px", body=[
#button { id=mybutton, text="Submit", postback=click },
#panel { id=placeholder, body=[ #p { body=io_lib:format("~B", [wf:state(click_counter)]) } ] }
]}.

event(click) ->
wf:state(click_counter, wf:state(click_counter)+1),
wf:update(placeholder, [
#p { body=io_lib:format("~B", [wf:state(click_counter)]) }
]).

...which compiles, but generates this error:

=INFO REPORT==== 15-Jan-2011::13:15:13 ===
{error,error,badarg,
[{io_lib,format,["~B",[undefined]]},
{my_page,body,0},
{element_function,call_next_function,1},
{wf_render_elements,call_element_render,2},
{wf_render_elements,render_element,1},
{wf_render_elements,render_elements,2},
{lists,foldl,3},
{wf_render_elements,render_elements,2}]}

If I replace the initial value reference, wf:state('click_counter') with the constant 0, the page renders, but it doesn't update. The page source shows a script that looks like it fires, but the only effect is to generate this message in the Firefox console:

Warning: Unknown pseudo-class or pseudo-element 'input'.
Source File: http://localhost:8000/my/page
Line: 0

Quick research into the "onpageload" event shows that it's different between IE and everyone else (IE: document.attachEvent('onload',startClock);; everyone else: document.addEventListener('load',startClock,false);). That looks like a dead end.

OK, I give up.

Slide 47: Page State (answer to exercise)


O.K. So wf:state_default/2 doesn't work at all as I imagined. Instead, if the atom's state is not defined by the page, it returns the default, and if it is defined by the page it returns that value. I like this minor variant on their answer better:

body() ->
#panel { style="margin: 50px", body=[
#button { id=mybutton, text="Submit", postback=click },
#panel { id=placeholder, body="0" }
]}.

event(click) ->
Counter = wf:state_default(click_counter, 0) + 1,
wf:update(placeholder, [
#panel { body=wf:f("~B", [Counter]) }
]),
wf:state(click_counter, Counter).

It appears that the Firefox console complaint is "normal" - the page is working as expected, but the complaint still occurs.

Nitrogen, an Erlang web application library/framework Part 3

Slide 28: What is a Nitrogen Action?


I'm feeling increasingly perturnbed by these form elements used outside a form. Can you tell I'm an HTML newbie? After implementing the example and refreshing the page, the alert fires immediately, which surprised me some. I'm going to guess that the submit button will be effectively a refresh.

Nope. Apparently it's a no-op. The alert didn't fire on clicking Submit. I'm going to try wireshark on the loopback interface to see if there was any browser traffic.

OK; it seems neither the submit button nor the alert generates any activity from the browser.

Slide 29: What is a Nitrogen Action?


So "alert('hello');" is the same thing as #alert { text="hello" }.

Does page source differ? Except for generated names, no. Something I didn't know before: when Firefox does a "save page as" it stores the referenced javascript files and alters the page to refer to the store version. Very useful.

Slide 30: Wiring an action


First use of #effect. pulsate fades the element in and out four times, then fades it in permanently.

Slide 31: Conditional Actions with #event{}


I infer that if I wanted the alert to occur when "Submit" in the previous examples was clicked, then it should have been attached to a click event. First, the example as shown.

OK, let's associate the alert with the click:

body() ->
wf:wire(mybutton, #event {
type=click,
actions=#alert { text="hello" }
}),
[
#button { id = mybutton, text = "Submit" }
].

Yup, that worked.

Slide 32: Triggers and Targets


I found this opaque until running the example. Here's how I think of it:

  • Events Have Types, e.g., "click"

  • Event Types Have Triggers

  • Triggers Refer to Elements


Thus, an event of a specific type must happen within a specific element.


  • Events Have Actions

  • Actions Have Targets

  • Targets Refer to Elements


Slide 33: Triggers and Targets


Important summary.

Slide 37: My First postback


Message sent back to ("queued") to server.

Slide 38: Postback Shortcuts


Different elements, different implicit events.

Slide 40: More Event Examples


"OtherEvent" is misspelled as "MyEvent" in the example. Should look like:

event(my_click_event) ->
?PRINT({click, now()});
event(OtherEvent) ->
?PRINT({other, OtherEvent, now()}).

PROBLEM: only the mouseout event is generating a postback? Examining the page source, only the mouseout event appears to be defined. What am I doing wrong?

%% -*- mode: nitrogen -*-
-module (my_page).
-compile(export_all).
-include_lib("nitrogen/include/wf.hrl").
-include("records.hrl").

main() -> #template { file="./site/templates/bare.html" }.

title() -> "Hello from my_page.erl!".

body() ->
wf:wire(mybutton, [
#event { type=mouseover, postback=my_mouseover_event }
#event { type=click, postback=my_click_event }
#event { type=mouseout, postback=my_mouseout_event }
]),
[
#button { id=mybutton, text="Submit" }
].

event(my_click_event) ->
?PRINT({click, now()});
event(OtherEvent) ->
?PRINT({other, OtherEvent, now()}).

Thursday, January 13, 2011

Nitrogen, an Erlang web application library/framework Part 2

Slide 17


Why are we backslash escaping the underscore? Or is that backslash supposed to be a DOS directory separator? It will get removed by any Unix shell, so, at my peril, I'm going to omit it.


bash-4.1$ ./dev page my_page
Created page: ./site/src/my_page.erl
Remember to recompile!
bash-4.1$ emacs ../site/src/my_page.erl
^Z
[1]+ Stopped emacs ../site/src/my_page.erl
bash-4.1$ bg
[1]+ emacs ../site/src/my_page.erl &
bash-4.1$ pwd
/home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/my_page
:: MAKE - ./site
:: Done!


Seems to be working...

That's odd: tutorial says use port 8080 - how'd we switch from port 8000 to port 8080?

Browser says: Unable to connect. Let's try port 8000.

That worked.


Slide 19


Puzzling: "module:body()" seems to mean a module name is explicitly needed when interpolating(?) erlang "callouts", and "script" seems to imply that the script name is globally scoped within the browser (let's assume that erlang doesn't contain an internal javascript interpreter, ok?). Or is "script" supposed to also follow the "module:function(Args)" format, with the erlang result comprised of javascript code to be executed? Presumably this will be revealed...


Slide 20


Default template is what? "page:body()" doesn't occur in any version of site/src/my_page.erl nor site/src/index.erl. grep for it.


bash-4.1$ grep -Rl 'page:body()' ..
../site/templates/bare.html
../doc/org-mode/tutorial.html
../doc/org-mode/tutorial.org
../doc/html/tutorial.html

Ah, it must be ../site/templates/bare.html.

OK, that's me being dumb: site/templates/bare.html is explicitly referenced in index.erl and my_page.erl.

Doing the replacement suggested by the tutorial, without doing any compilation, removes all the page content, but provokes no error messages. Confirm by defining body1/0 in index.erl - which needs compilation. Interpolation confirmed.

"page" refers to the generic corresponding erlang module, e.g. "index.erl" or "my_page.erl" (for .../my/page). Any specific module:function can be referenced from within the template; an arbitrary expression causes a failure in "-convert_callback_tuple_to_function/4-fun-0-".

Slide 22

.
No surprises, although a <label> element normally occurs in a form, which isn't present here.

Slide 25


Again, where is the <form>?

Slide 26


A #panel is equivalent to a <div>.

Nitrogen, an Erlang web application library/framework Part 1

The point to this blog is to record my experience following various tutorials.
First up: http://nitrogenproject.com/doc/tutorial.html

I'm working on Fedora 14 with the erlang packages installed.

Fifth screen "Install & Run Nitrogen":


bash-4.1$ bin/nitrogen console
cat: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: Is a directory
bin/nitrogen: line 102: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: is a directory
Exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/erts-5.8.1/bin/erlexec -boot /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/releases/2.0.4/nitrogen -embedded -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/app.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/inets.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: -config dev -config nitrogen -args_file /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/vm.args -- console
Root: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen
bin/nitrogen: line 128: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: is a directory
bin/nitrogen: line 128: exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: cannot execute: Success


That "Success" at the bottom was difficult to see...

And firefox says "unable to connect"...

And netstat shows nothing listening to port 8000.


Aha! What works is


bash-4.1$ cd bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
bash-4.1$ ls
dev nitrogen
bash-4.1$ ./nitrogen
Usage: nitrogen {start|stop|restart|reboot|ping|console|attach}
bash-4.1$ ./nitrogen console
Exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/erts-5.8.1/bin/erlexec -boot /home/sps/erlang-libs/nitrogen/rel/nitrogen/releases/2.0.4/nitrogen -embedded -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/app.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/inets.config -args_file /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/vm.args -- console
Root: /home/sps/erlang-libs/nitrogen/rel/nitrogen
Erlang R14B (erts-5.8.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:5] [kernel-poll:true]

Eshell V5.8.1 (abort with ^G)
(nitrogen@127.0.0.1)1>


Slide 10, they don't mention that the "Welcome..." string occurs in two places. Why is that not an expanded variable?

I had aborted out to explore the directory tree from the command line, so I needed to start nitrogen again. Restarting did not automatically reflect the changes, so here we go: sync:go() from the command line:


bash-4.1$ ./nitrogen console
Exec: /home/sps/erlang-libs/nitrogen/rel/nitrogen/erts-5.8.1/bin/erlexec -boot /home/sps/erlang-libs/nitrogen/rel/nitrogen/releases/2.0.4/nitrogen -embedded -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/app.config -config /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/inets.config -args_file /home/sps/erlang-libs/nitrogen/rel/nitrogen/etc/vm.args -- console
Root: /home/sps/erlang-libs/nitrogen/rel/nitrogen
Erlang R14B (erts-5.8.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:5] [kernel-poll:true]

Eshell V5.8.1 (abort with ^G)
(nitrogen@127.0.0.1)1> sync:go().
:: MAKE - site
Recompile: ./src/index
:: MAKE - ./site
:: Done!
ok


That worked.


Slide 11: again, invoking
bin/dev compile
from its parent directory failed.

But invoking from within the bin directory succeeds:


bash-4.1$ bin/dev compile
cat: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: Is a directory
bin/dev: line 60: /home/sps/erlang-libs/nitrogen/rel/nitrogen/bin: is a directory
Node is not running!
bash-4.1$ pwd
/home/sps/erlang-libs/nitrogen/rel/nitrogen
bash-4.1$ cd bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/index
:: MAKE - ./site
:: Done!


Slide 12: doesn't say where in the file to add those lines; I'm going to guess that ?DEBUG is on its own line immediately after the -include_libs on the fourth line.

Bad guess:


bash-4.1$ cd bin
/home/sps/erlang-libs/nitrogen/rel/nitrogen/bin
bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/index
./src/index.erl:5: syntax error before: ':'
:: Errors!


That looks like a macro expansion. Let's google for an example in other code...

No specific example, but let's try adding this line instead:

-define(DEBUG, 1)


Nope:


bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/index
./src/index.erl:5: redefining macro 'DEBUG'
:: Errors!


Next, I'll try adding ?DEBUG to the inner_body() list.


bash-4.1$ ./dev compile
:: MAKE - site
Recompile: ./src/index
:: MAKE - ./site
:: Done!

Promising...

But this is all that came back from a browser refresh:

Internal Server Error


This is what is on the console:

=INFO REPORT==== 13-Jan-2011::16:19:53 ===
DEBUG: index:29

=INFO REPORT==== 13-Jan-2011::16:19:53 ===
{error,throw,
{unanticipated_case_in_render_elements,ok},
[{wf_render_elements,render_elements,2},
{lists,foldl,3},
{wf_render_elements,render_elements,2},
{lists,foldl,3},
{wf_render_elements,render_elements,2},
{wf_render_elements,call_element_render,2},
{wf_render_elements,render_element,1},
{wf_render_elements,render_elements,2}]}


Maybe that was supposed to happen. Try restarting the server...

Same result.

Instead of ?DEBUG try "?DEBUG": renders the page with the literal string ?DEBUG on it. I suppose ?DEBUG should be when the analog to an assert fails?

Trying ?PRINT(node()) instead of "?DEBUG":


=INFO REPORT==== 13-Jan-2011::16:30:29 ===
DEBUG: index:30
"node ( )"
'nitrogen@127.0.0.1'

=INFO REPORT==== 13-Jan-2011::16:30:29 ===
{error,throw,
{unanticipated_case_in_render_elements,ok},
[{wf_render_elements,render_elements,2},
{lists,foldl,3},
{wf_render_elements,render_elements,2},
{lists,foldl,3},
{wf_render_elements,render_elements,2},
{wf_render_elements,call_element_render,2},
{wf_render_elements,render_element,1},
{wf_render_elements,render_elements,2}]}


inner_body() doesn't seem to like these macros as part of its list argument; how about as an result-ignored function at the beginning of its definition?

Much better! Page renders, and console simply says:

DEBUG: index:16
"node ( )"
'nitrogen@127.0.0.1'


Slide 13: first attempt at .emacs config generates Symbol's function definition is void: erlang-mode on attempting M-x nitrogen-mode. Try to add erlang mode...

Hmmm. Fedora didn't include the emacs erlang mode in the erlang super-package. So:


sudo yum -y install emacs-erlang-el.noarch emacs-erlang.noarch

OK, now its working.