Introduction to Nitrogen
Table of Contents
- 1 Welcome
- 2 Main Agenda Slide
- 3 PART 1 AGENDA
- 4 Install Nitrogen
- 5 Start the Website in Console Mode
- 6 Stop the Website
- 7 Anatomy of a Nitrogen Project
- 8 Anatomy of the site/ Directory
- 9 Anatomy of the site/src Directory
- 10 Exercise: Modify a Nitrogen Page
- 11 Exercise: Compile in Different Ways
- 12 Dynamic Compiling
- 13 Exercise: Debugging
- 14 Emacs Mode
- 15 PART 2 AGENDA
- 16 What is a Nitrogen Page?
- 17 Dynamic Routes Explained
- 18 Creating a New Page
- 19 How is a Page Rendered (Simple Version)
- 20 Anatomy of a Template
- 21 Experimenting With Templates
- 22 PART 3 AGENDA
- 23 What is a Nitrogen Element?
- 24 What is a Nitrogen Element?
- 25 Nitrogen Element Properties
- 26 Nitrogen Element Examples
- 27 Nested Elements
- 28 PART 4 AGENDA
- 29 What is a Nitrogen Action?
- 30 What is a Nitrogen Action?
- 31 Wiring an Action
- 32 Conditional Actions with
#event{}
- 33 Triggers and Targets
- 34 Triggers and Targets
- 35 Quick Review
- 36 PART 5 AGENDA
- 37 What is a Postback?
- 38 Your First Postback
- 39 Postback Shortcuts
- 40 Postback Shortcuts
- 41 More Event Examples
- 42 More Event Examples
- 43 Modifying Elements
- 44 More Page Manipulation
- 45 PART 6 AGENDA
- 46 Page State vs. Session State
- 47 Page State
- 48 Page State
- 49 Session State
- 50 Session State
- 51 PART 7 AGENDA
- 52 Limiting Access to a Page
- 53 Authentication and Authorization Functions
- 54 Page Redirection Functions
- 55 Creating a Secure Page - Step 1
- 56 Creating a Secure Page - Step 2
- 57 Creating a Secure Page - Step 3
- 58 Creating a Secure Page - Step 4
- 59 PART 8 AGENDA
- 60 Overview of Nitrogen Validation
- 61 Overview of Nitrogen Validation
- 62 Overview of Nitrogen Validation
- 63 Overview of Nitrogen Validation
- 64 PART 9 AGENDA
- 65 What is Comet?
- 66 Comet the Nitrogen/Erlang Way
- 67 A Comet Counter
- 68 Comet Pools
- 69 Comet Pool Scope
- 70 The Simplest Chatroom Ever Constructed
- 71 PART 10 AGENDA
- 72 Custom Elements - Part 1
- 73 Custom Elements - Part 2
- 74 Custom Elements - Part 3
- 75 Custom Elements - Part 4
- 76 Custom Actions - Part 1
- 77 Custom Actions - Part 2
- 78 Custom Actions - Part 3
- 79 Handlers - Part 1
- 80 Handlers - Part 2
- 81 Handlers - Part 3
- 82 Handlers - Part 3
- 83 Conclusion
- 84 Conclusion
1 Welcome
Introduction to Nitrogen
major features and concepts behind
the Nitrogen Web Framework.
2 Main Agenda Slide
2.1 Agenda
- Part 1: Install & Run Nitrogen
- Part 2: Nitrogen Pages
- Part 3: Nitrogen Elements
- Part 4: Nitrogen Actions
- Part 5: Nitrogen Postback Events
- Part 6: Session and Page State
- Part 7: Security
- Part 8: Validation
- Part 9: Comet
- Part 10: Extending Nitrogen
- Conclusion
3 PART 1 AGENDA
3.1 Install & Run Nitrogen
- Install Nitrogen
- Run the Website
- A Tour Through the Files
4 Install Nitrogen
4.1 Install Nitrogen
4.1.1 If you don't have Erlang Installed:
Download Nitrogen, unzip and cd nitrogen
.
4.1.2 If you do have Erlang installed:
Pull the Nitrogen Source Code, then make rel_inets; cd rel/nitrogen
.
5 Start the Website in Console Mode
5.1 Install & Run Nitrogen
5.1.1 Start Up
bin/nitrogen console
Open http://localhost:8000 in your Browser
6 Stop the Website
6.1 Install & Run Nitrogen
6.1.1 Shut Down
Press Control-C twice.
6.1.2 View the Directory
ls -l
7 Anatomy of a Nitrogen Project
7.1 Install & Run Nitrogen
7.1.1 Anatomy of a Nitrogen Project
- BuildInfo.txt
- From
uname
. - Makefile
- Used by
make
. - bin/
- Commands to start and stop system, plus developer tools.
- etc/
- Configuration settings.
- site/
- Contains the website files, templates, and Erlang modules.
- log/
- The logs.
- erts-X.Y.Z/
- Embedded Erlang.
- releases/
- Tells Erlang how to start the system.
- lib/
- Dependent libraries.
8 Anatomy of the site/ Directory
8.1 Install & Run Nitrogen
8.1.1 The site/
Directory
The site directory should go under source control, it contains all of the information necessary to run the website.
- Emakefile
- Used by make.erl to compile the system.
- ebin/
- Compiled Erlang modules.
- include/
- Include files for your website.
- src/
- Erlang source files for your website.
- static/
- Static files for your website.
- templates/
- Template files for your website.
9 Anatomy of the site/src Directory
9.1 Install & Run Nitrogen
9.1.1 The site/src/
Directory
Stores the Erlang source files for your application. By default it contains:
- nitrogen_init.erl
- Runs once on Nitrogen startup.
- nitrogen_PLATFORM.erl
- Holds the request loop depending on platform.
- index.erl
- The default web page.
- elements/
- By convention, custom alements are placed here.
- actions/
- By convention, custom actions are placed here.
10 Exercise: Modify a Nitrogen Page
10.1 Install & Run Nitrogen
10.1.1 Exercise: Modify Your First Page
- From the Erlang Shell, run:
sync:go()
- Open
site/src/index.erl
- Change "Welcome to Nitrogen" to "Welcome to My Website"
- Reload the page
11 Exercise: Compile in Different Ways
11.1 Install & Run Nitrogen
11.1.1 Exercise: Compile in a Different Way
- From a different terminal, run:
bin/dev compile
- Change to "Welcome to my ERL-TASTIC WEBSITE!" (or, you know, whatever)
- Reload the page
12 Dynamic Compiling
12.1 Install & Run Nitrogen
12.1.1 Understanding sync
- Running
sync:go()
from the Erlang shell orbin/dev compile
start thesync
application - Sync applications constantly checks for changes to Erlang files and attempts to recompile
- To stop sync's checking, run
sync:stop()
- Note: Sync will only recompile files changed since sync was launched.
Sync is not aware of changes made before running
sync:go()
13 Exercise: Debugging
13.1 Install & Run Nitrogen
13.1.1 Debug Statements
- Add
?DEBUG
toindex.erl
. Then compile and reload. What happens? - Add
?PRINT(node())
toindex.erl
. Then compile and reload. What happens?
14 Emacs Mode
14.1 Install & Run Nitrogen
14.1.1 Emacs nitrogen-mode
(add-to-list 'load-path "PATH/TO/NITROGEN/support/nitrogen-mode") (require 'nitrogen-mode)
Without nitrogen-mode
:
#panel { id=my_panel, body=[ #panel { id=my_panel2, body=[ #label { text="Name" }, #textbox { id=my_textbox } ]} ]}
With nitrogen-mode
:
M-x nitrogen-mode #panel { id=my_panel, body=[ #panel { id=my_panel2, body=[ #label { text="Name" }, #textbox { id=my_textbox } ]} ]}
15 PART 2 AGENDA
15.1 Nitrogen Pages
- What is a Nitrogen Page?
- Dynamic Routing Explained
- Creating Your First Page
- How is a Page Rendered?
- Anatomy of a Template
- Experimenting With Templates
16 What is a Nitrogen Page?
16.1 Nitrogen Pages
16.1.1 What is a Nitrogen Page
- A Page is an Erlang Module
- Each page should accomplish one store or piece of
functionality.
Some examples:
- Allow the user to log in (
user_login.erl
). - Change the user's preferences. (
user_preferences.erl
) - Display a list of items. (
items_view.erl
) - Allow the user to edit an item. (
items_edit.erl
)
- Allow the user to log in (
17 Dynamic Routes Explained
17.1 Nitrogen Pages
17.1.1 Dynamic Routing Explained
Dynamic routing rules:
- If there is an extension, assume a static file.
http://localhost:8000/routes/to/a/module http://localhost:8000/routes/to/a/static/file.html
- Root page maps to
index.erl
- Replaces slashes with underscores.
http://localhost:8000/routes/to/a/module -> routes_to_a_module.erl
- Try the longest matching module.
http://localhost:8000/routes/to/a/module/foo/bar -> routes_to_a_module.erl
- Modules that aren't found go to
web_404.erl
if it exists. - Static files that aren't found are handled by the underlying platform (not yet generalized.)
18 Creating a New Page
18.1 Nitrogen Pages
18.1.1 Exercise: Create a New Page
- Generate the Page
bin/dev page my_page $EDIT site/src/my_page.erl
- Replace the default body with:
body() -> "Hello World!".
- Remove the
event/1
function. - Compile the page and load
http://localhost:8080/my/page
19 How is a Page Rendered (Simple Version)
19.1 Nitrogen Pages
19.1.1 How is a Page Rendered?
- User hits a URL.
- URL is mapped to a module.
- Nitrogen framework calls
module:main()
module:main()
calls a#template
#template
calls back into the page (or other modules)- Nitrogen framework renders the output into HTML/Javascript.
(This is the simple version. Complex version will come later.)
20 Anatomy of a Template
20.1 Nitrogen Pages
20.1.1 Anatomy of a Template
- HTML. The Page is slurped into the Template.
- Contains one or more callouts, ie:
[[[module:body()]]]
- Contains a script callout for Javascript:
[[[script]]]
- The callouts look like Erlang, but they are not. They can only be
of the form
module:function(Args)
. The 'page' module refers to the current page.
21 Experimenting With Templates
21.1 Nitrogen Pages
21.1.1 Experimenting With Templates
- Change the callout from
page:body()
topage:body1()
in the default template and reload the page. What happens? - Create another callout. What happens?
- What happens when you change
page
to be a specific module? - Replace the module call with some arbitrary Erlang code. What happens?
22 PART 3 AGENDA
22.1 Nitrogen Elements
- What is a Nitrogen Element?
- Add Elements to Your Page
- Nested Elements
- Documentation
- Anatomy of a Nitrogen Element
23 What is a Nitrogen Element?
23.1 Nitrogen Elements
23.1.1 What is a Nitrogen Element?
An element can be either HTML, or some record that renders into HTML.
Change this:
body() -> "Hello World!".
To this:
body() -> #label { text="Hello World!" }.
24 What is a Nitrogen Element?
24.1 Nitrogen Elements
24.1.1 What is a Nitrogen Element?
The #label{}
element is rendered into:
<label class="wfid_tempNNNNN label">Hello World!</label>
View the rendered page source in your browser and search for "Hello World".
25 Nitrogen Element Properties
25.1 Nitrogen Elements
25.1.1 Why Nitrogen Elements?
Nitrogen elements serve two purposes:
- Allow you to generate HTML within Erlang:
- Avoid mixing languages == clearer code.
- Fewer characters to type.
- Checked at compile time.
- Abstraction layer:
- Avoid repeating common functionality.
- Hide complexity in a module.
26 Nitrogen Element Examples
26.1 Nitrogen Elements
26.1.1 Nitrogen Element Examples
Try this on my_page.erl:
body() -> [ #h1 { text="My Simple Application" }, #label { text="What is your name?" }, #textbox { }, #button { text="Submit" } ].
Then compile, reload, and view source.
27 Nested Elements
27.1 Nitrogen Elements
27.1.1 Nested Elements
Try a nested element:
body() -> #panel { style="margin: 50px;", body=[ #h1 { text="My Page" }, #label { text="Enter Your Name:" }, #textbox { }, #button { text="Submit" } ]}.
28 PART 4 AGENDA
28.1 Nitrogen Actions
- What is a Nitrogen Action?
- Wiring an Action
- Conditional Actions with
#event{}
- Postbacks
29 What is a Nitrogen Action?
29.1 Nitrogen Actions
29.1.1 What is a Nitrogen Action?
An action can either be Javascript, or some record that renders into Javascript.
Add a Javascript alert to the #button{}
element. Then recompile
and run. What do you expect will happen?
body() -> [ #button { text="Submit", actions=[ #event{type=click,actions="alert('hello');" } ]} ].
30 What is a Nitrogen Action?
30.1 Nitrogen Actions
30.1.1 What is a Nitrogen Action?
Do the same thing a different way.
body() -> [ #button { text="Submit", actions=[ #event{type=click, actions=#alert { text="Hello" } ]} ].
31 Wiring an Action
31.1 Nitrogen Actions
31.1.1 Wiring an Action
Setting the actions
property of an element can lead to messy
code. Another, cleaner way to wire an action is the wf:wire/N
function.
body() -> wf:wire(mybutton, #effect { effect=pulsate }), [ #button { id=mybutton, text="Submit" } ].
32 Conditional Actions with #event{}
32.1 Nitrogen Actions
32.1.1 Conditional Actions with #event{}
Put the #effect{}
action inside of an #event{}
action. This
causes the effect to only get fired if the user clicks on
mybutton
.
body() -> wf:wire(mybutton, #event { type=click, actions=#effect { effect=pulsate } }), [ #button { id=mybutton, text="Submit" } ].
33 Triggers and Targets
33.1 Nitrogen Actions
33.1.1 Triggers and Targets
All actions have a target
property. The target
specifies what
element(s) the action effects.
The event action also has a trigger
property. The trigger
specifies what element(s) trigger the action.
Try this:
body() -> wf:wire(#event { type=click, trigger=mybutton, target=mylabel, actions=#effect { effect=pulsate } }), [ #label { id=mylabel, text="Make Me Blink!" }, #button { id=mybutton, text="Submit" } ].
34 Triggers and Targets
34.1 Nitrogen Actions
34.1.1 Triggers and Targets
You can also specify the Trigger and Target directly in wf:wire/N
. It takes three forms:
% Specify a trigger and target. wf:wire(Trigger, Target, Actions) % Use the same element for both trigger and target. wf:wire(TriggerAndTarget, Actions) % Assume the trigger and/or target is provided in the actions. % If not, then wire the action directly to the page. % (Useful for catching keystrokes.) wf:wire(Actions)
35 Quick Review
35.1 Nitrogen Actions
35.1.1 Quick Review
- Elements make HTML.
- Actions make Javascript.
- An action can be wired using the
actions
property, or wired later withwf:wire/N
. Both approaches can take a single action or a list of actions. - An action looks for
trigger
andtarget
properties. These can be specified in a few different ways. - Everything we have seen so far happens on the client.
36 PART 5 AGENDA
36.1 Nitrogen Events
- What is a Postback?
- Your First Postback
- Event Properties
- More Event Examples
- Postback Shortcuts
- Modifying Elements
37 What is a Postback?
37.1 Nitrogen Events
37.1.1 What is a Postback?
A postback briefly transfers control from the browser to the
Nitrogen server. It is initiated when an event fires with the
postback
property set. For example:
#event { type=click, postback=my_click_event }
The postback tag can be any valid Erlang term. You use this to differentiate incoming events.
38 Your First Postback
38.1 Nitrogen Events
38.1.1 Your First Postback
First, let's use the postback to print out a debug message.
body() -> wf:wire(mybutton, #event { type=click, postback=myevent }), [ #button { id=mybutton, text="Submit" } ]. event(myevent) -> ?PRINT({event, now()}).
39 Postback Shortcuts
39.1 Nitrogen Events
39.1.1 Postback Shortcuts
A few elements allow you to set the postback
property as a
shortcut to handle their most common events.
Element | Shortcut Event |
#button{} | click |
#textbox{} | enter key |
#checkbox{} | click |
#dropdown{} | change |
#password{} | enter key |
40 Postback Shortcuts
40.1 Nitrogen Events
40.1.1 Postback Shortcuts
A few elements allow you to set the postback
property as a
shortcut to handle their most common events.
The previous code, simplified:
body() -> [ #button { id=mybutton, text="Submit", postback=myevent } ]. event(myevent) -> ?PRINT({event, now()}).
41 More Event Examples
41.1 Nitrogen Events
41.1.1 More Event Examples
body() -> % 'mouseover', 'click', and 'mouseout' are standard Javascript % events. 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()}).
42 More Event Examples
42.1 Nitrogen Events
42.1.1 More Event Examples
Generally, a postback is a good chance to read form elements. The
wf:q(ElementID)
function does this.
body() -> [ #textbox { id=mytextbox, text="Edit this text." }, #button { id=mybutton, text="Submit", postback=myevent } ]. event(myevent) -> Text = wf:q(mytextbox), ?PRINT({event, Text}).
43 Modifying Elements
43.1 Nitrogen Events
43.1.1 Modifying Elements
Here is where everything comes together: we are going to modify the page from within a postback event. Nitrogen uses AJAX to update parts of a page without updating the entire page.
body() -> #panel { style="margin: 50px;", body=[ #button { id=mybutton, text="Submit", postback=click }, #panel { id=placeholder, body="This text will be replaced" } ]}. event(click) -> wf:update(placeholder, [ #h1 { text="Congratulations!" }, #p { body="You have updated the page!" }, #p { body=io_lib:format("~p", [now()]) } ]).
44 More Page Manipulation
44.1 Nitrogen Events
44.1.1 More Page Manipulation
The wf
module exposes many manipulation functions:
wf:update/2
- Update the contents of an element with another element(s).
wf:insert_top/2
- Insert a new element(s) at the beginning of another element.
wf:insert_bottom/2
- Insert a new element(s) at the bottom of another element.
wf:replace/2
- Replace an element with another element.
wf:remove/1
- Remove an element(s).
wf:set/2
- Set a textbox or checkbox value.
It also exposes many other generally useful utility functions: http://nitrogenproject.com/doc/api.html
45 PART 6 AGENDA
45.1 Remembering State
- Page State vs. Session State
- Page State Example
- Session State Example
46 Page State vs. Session State
46.1 Remembering State
46.1.1 Page State vs. Session State
Nitrogen can store two kinds of state:
- Page State
- Stored in a user's browser window.
- Destroyed when the user closes the window or navigates to a different page.
- Sent across the wire with each request.
- Session State
- Stored in server memory.
- Destroyed when the session expires or the Erlang VM dies.
- Associated with the user's session by an HTTP cookie.
- Useful place to store authentication
47 Page State
47.1 Remembering State
47.1.1 Page State
Using Page State:
% Set a state variable wf:state(Key, Value) % Get a state variable wf:state(Key) wf:state_default(Key, DefaultValue)
Key
and Value
can be any valid Erlang term.
Exercise: Modify my_page.erl to display a counter that gets incremented every time you press the 'Submit' button. The counter should reset when the user reloads the page.
48 Page State
48.1 Remembering State
48.1.1 Page State
body() -> #panel { style="margin: 50px;", body=[ #button { id=mybutton, text="Submit", postback=click }, #panel { id=placeholder, body="1" } ]}. event(click) -> Counter = wf:state_default(counter, 1), wf:update(placeholder, [ #panel { body=io_lib:format("~p", [Counter + 1]) } ]), wf:state(counter, Counter + 1).
49 Session State
49.1 Remembering State
49.1.1 Session State
Using Session State:
% Set a session state variable wf:session(Key, Value) % Get a session state variable wf:session(Key) wf:session_default(Key, DefaultValue)
Key
and Value
can be any valid Erlang term.
Exercise: Modify my_page.erl to display TWO counters. When the user presses the 'Submit' button, one counter should get incremented, the other counter should get doubled. The server should remember the counters even if the user closes and then re-opens the browser.
50 Session State
50.1 Remembering State
50.1.1 Session State
body() -> #panel { style="margin: 50px;", body=[ #button { id=mybutton, text="Submit", postback=click }, #panel { id=placeholder1, body="1" }, #panel { id=placeholder2, body="1" } ]}. event(click) -> %% Increment the counter... Counter1 = wf:session_default(counter1, 1), wf:update(placeholder1, io_lib:format("~p", [Counter1 + 1])), wf:session(counter1, Counter1 + 1), %% Double the other counter... Counter2 = wf:session_default(counter2, 1), wf:update(placeholder2, io_lib:format("~p", [Counter2 * 2])), wf:session(counter2, Counter2 * 2).
51 PART 7 AGENDA
51.1 Security
- Limiting Access to a Page
- Authentication and Authorization Functions
- Page Redirection Functions
- Creating a Secure Page
52 Limiting Access to a Page
52.1 Security
52.1.1 Limiting Access to a Page
Nitrogen contains functions to help you build password protected websites:
- Nitrogen is built for role-based security. You set the roles for
a current session, and check those roles later.
For example, the user may have the
friend
andmanager
roles, but not theadministrator
role. - Authentication/authorization info is stored in the session.
53 Authentication and Authorization Functions
53.1 Security
53.1.1 Authentication and Authorization Functions
Functions to set the user/role:
% Get/set the current user for this session. wf:user(), wf:user(User) % Get/set whether the current session has the specified role. wf:role(Role), wf:role(Role, IsInRole)
54 Page Redirection Functions
54.1 Security
54.1.1 Page Redirection Functions
Functions kick the user to a login page:
% Redirect the user to a different page. wf:redirect(Url) % Redirect the user to the login page. wf:redirect_to_login(LoginUrl) % Redirect the user back to the original page they % tried to access. wf:redirect_from_login(DefaultUrl)
55 Creating a Secure Page - Step 1
55.1 Security
55.1.1 Creating a Secure Page - Step 1
Check for the managers
role at the top of a page. If the user
doesn't have the role, go to a login page.
main() -> case wf:role(managers) of true -> #template { file="./site/templates/bare.html" }; false -> wf:redirect_to_login("/login") end.
56 Creating a Secure Page - Step 2
56.1 Security
56.1.1 Creating a Secure Page - Step 2
Create a login page. For now, just create a button that, when
clicked, grants the managers
role to the user and redirects
back.
body() -> #button { text="Login", postback=login }. event(login) -> wf:role(managers, true), wf:redirect_from_login("/").
57 Creating a Secure Page - Step 3
57.1 Security
57.1.1 Creating a Secure Page - Step 3
Update login.erl
to prompt for a username and password.
body() -> #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) -> case wf:q(password) == "password" of true -> wf:role(managers, true), wf:redirect_from_login("/"); false -> wf:flash("Invalid password.") end.
58 Creating a Secure Page - Step 4
58.1 Security
58.1.1 Creating a Secure Page - Step 4
Create a way for the user to logout.
% Clears all user, roles, session state, and page state. wf:logout()
Note: Placing this statement appropriately is left as an exercise for the reader.
59 PART 8 AGENDA
59.1 Validation
- Overview of Nitrogen Validation
- Adding Some Validators
60 Overview of Nitrogen Validation
60.1 Validation
60.1.1 Overview of Nitrogen Validation
Nitrogen implements a validation framework, plus a number of pre-built validators, to allow you to declaratively validate your form variables.
Validation happens on both client side (using the LiveValidation library) and server side (in Erlang).
This is done to present a responsive front end to the user
61 Overview of Nitrogen Validation
61.1 Validation
61.1.1 Overview of Nitrogen Validation
The simplest validator is the #is_required{}
validator. Tell your
login.erl
page to make sure the user enters both a username and
a password.
body() -> wf:wire(submit, username, #validate { validators=[ #is_required { text="Required." } ]}), wf:wire(submit, password, #validate { validators=[ #is_required { text="Required." } ]}), #panel { style="margin: 50px;", body=[ ...
62 Overview of Nitrogen Validation
62.1 Validation
62.1.1 Overview of Nitrogen Validation
We can get clever and use a validator to check that the user
entered the correct password. The #custom{}
validator runs on
the server. (To make a custom client-side validator, use
#js_custom{}
.)
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=[ ...
63 Overview of Nitrogen Validation
63.1 Validation
63.1.1 Overview of Nitrogen Validation
Since we validate the password in the #custom
validator, we can
trust that the login
event only fires when the password is
correct. Change the login
event to:
event(login) -> wf:role(managers, true), wf:redirect_from_login("/").
64 PART 9 AGENDA
64.1 Comet
- What is Comet?
- Comet the Nitrogen/Erlang Way
- A Comet Counter
- Comet Pools
- Comet Pool Scope
- The Simplest Chatroom Ever Constructed
65 What is Comet?
65.1 Comet
65.1.1 What is Comet?
Comet is the name for a technique where the browser requests something from the server, and the server doesn't respond until it has something useful to say.
This makes it useful for applications that need fast, out-of-band communication, such as chat rooms.
In other words, you don't need to keep hitting a "Get Messages" button. The server just pushes messages when they are available.
A big happy shout out to Tom McNulty for his innovative ideas on what Comet support could look like in Nitrogen.
66 Comet the Nitrogen/Erlang Way
66.1 Comet
66.1.1 Comet the Nitrogen/Erlang Way
Think of Comet like erlang:spawn/1
:
- Start up a function.
- The function can manipulate the page using
wf:update/2
or any other page manipulation function. - Output is queued until the function ends or calls
wf:flush/0
. - The function acts like it is linked to the current user's
page. It is killed when the user leaves the page (or receives
{'EXIT', _, Message}
iftrap_exit
istrue
.)
67 A Comet Counter
67.1 Comet
67.1.1 A Comet Counter
Update my_page.erl
to count once per second.
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).
68 Comet Pools
68.1 Comet
68.1.1 Comet Pools
You can tell a Comet function to start in a pool by providing a
PoolName
. The PoolName
can be any Erlang term.
wf:comet(Fun, PoolName)
Now you can send messages to the pool. The messages will be received by other functions started in that comet pool.
wf:send(PoolName, Message)
69 Comet Pool Scope
69.1 Comet
69.1.1 Comet Pool Scope
So far, we've been creating local comet pools. Nitrogen also has the idea of global comet pools:
- Local comet pools are walled around the current page and the current user. If the user reloads the page, the comet process(es) goes away.
- Global comet pools exist to help you create multi-user applications. They pool is accessible by all pages and all users.
%% Create a global comet pool. wf:comet_global(Function, PoolName) %% Send a global comet message. wf:send_global(PoolName, Message)
70 The Simplest Chatroom Ever Constructed
70.1 Comet
70.1.1 The Simplest Chatroom Ever Constructed
Here we're going to create a page that listens for some text, and sends it to the global comet pool. Connect with different browsers and chat to yourself.
body() -> wf:comet_global(fun() -> repeater() end, repeater_pool), [ #textbox { id=msg, text="Your message...", next=submit }, #button { id=submit, text="Submit", postback=submit }, #panel { id=placeholder } ]. event(submit) -> ?PRINT(wf:q(msg)), wf:send_global(repeater_pool, {msg, wf:q(msg)}). repeater() -> receive {msg, Msg} -> wf:insert_top(placeholder, [Msg, "<br>"]) end, wf:flush(), repeater().
71 PART 10 AGENDA
71.1 Extending Nitrogen
- Custom Elements
- Custom Actions
- Handlers
72 Custom Elements - Part 1
72.1 Extending Nitrogen
72.1.1 Custom Elements - Part 1
You can create custom elements to encapsulate other elements. There is no difference between a custom element and a built-in element, except where the actual files are stored.
Create a new custom element in site/src/elements/my_element.erl
.
./bin/dev element my_element
73 Custom Elements - Part 2
73.1 Extending Nitrogen
73.1.1 Custom Elements - Part 2
An element has:
- A record containing the properties of the element.
- A
reflect()
function, providing a programattic way to get the properties of an element. Ifrecord_info(fields, RecordType)
worked, this would not be necessary.) - A
render_element(Record)
function that emits HTML or other elements.
74 Custom Elements - Part 3
74.1 Extending Nitrogen
74.1.1 Custom Elements - Part 3
Let's make an element that displays a textbox and a button, logs the result of the textbox to the console, and then calls a method on the main page.
render_element(#my_element{}) -> TextboxID = wf:temp_id(), ButtonID = wf:temp_id(), wf:wire(ButtonID, #event { type=click, delegate=?MODULE, postback={click, TextboxID} }), [ #textbox { id=TextboxID, text="Your text...", next=ButtonID }, #button { id=ButtonID, text="Submit" } ]. event({click, TextboxID}) -> Text = wf:q(TextboxID), ?PRINT({clicked, TextboxID, Text}), PageModule = wf:page_module(), PageModule:my_element_event(Text).
75 Custom Elements - Part 4
75.1 Extending Nitrogen
75.1.1 Custom Elements - Part 4
Now, use the element on my_page.erl
. Remember to move the
element into include/records.hrl
first!
body() -> #my_element {}. my_element_event(Text) -> ?PRINT(Text).
For more examples, see the built-in elements under nitrogencore/src/elements.
76 Custom Actions - Part 1
76.1 Extending Nitrogen
76.1.1 Custom Actions - Part 1
A custom action is like a custom element, except it should emit Javascript or other actions.
./bin/dev action my_action
77 Custom Actions - Part 2
77.1 Extending Nitrogen
77.1.1 Custom Actions - Part 2
Let's make a custom action that calls #alert{}
with a specified
string, but converted to all uppercase.
-record(my_action, {?ACTION_BASE(action_my_action), text}). render_action(Record = #my_action{}) -> #alert { text=string:to_upper(Record#my_action.text) }.
78 Custom Actions - Part 3
78.1 Extending Nitrogen
78.1.1 Custom Actions - Part 3
Now, use the element on my_page.erl
. Remember to move the action
into include/records.hrl
first!
body() -> wf:wire(#my_action { text="this is a message" }), #label { text="You should see an alert." }.
For more examples, see the built-in actions under nitrogencore/src/actions.
79 Handlers - Part 1
79.1 Extending Nitrogen
79.1.1 Handlers - Part 1
Handlers are an attempt to formalize an approach for overriding core Nitrogen behavior.
Handlers exist for:
- Configuration
- Logging
- Process Registry
- Caching
- Session Storage
- Page State Storage
- User Identity
- Roles
- Routing
- Security
80 Handlers - Part 2
80.1 Extending Nitrogen
80.1.1 Handlers - Part 2
Handlers are initialized in the order described on the previous page. This means that any handler can access and override information defined by a handler that came before it.
For example, you could write a route_handler
that behaved
differently depending on the role of a user.
81 Handlers - Part 3
81.1 Extending Nitrogen
81.1.1 Handlers - Part 3
Let's make a security_handler
handler that only allows the user
to access modules beginning with the word "my".
-module(my_security_handler). -behaviour(security_handler). -export([init/2, finish/2]). -include_lib("nitrogen_core/include/wf.hrl"). init(_Config, State) -> ?PRINT(wf:page_module()), case wf:to_list(wf:page_module()) of "my" ++ _ -> {ok, State}; "static_file" -> {ok, State}; _ -> wf_context:page_module(access_denied), {ok, State} end. finish(_Config, State) -> {ok, State}.
82 Handlers - Part 3
82.1 Extending Nitrogen
82.1.1 Handlers - Part 3
Now, install the handler in nitrogen_inets.erl
:
do(Info) -> RequestBridge = simple_bridge:make_request(inets_request_bridge, Info), ResponseBridge = simple_bridge:make_response(inets_response_bridge, Info), nitrogen:init_request(RequestBridge, ResponseBridge), nitrogen:handler(my_security_handler, []), nitrogen:run().
83 Conclusion
83.1 Conclusion
By now, you should have a basic understanding of how Nitrogen works, and know enough to be able to quickly grok the examples on http://nitrogenproject.com and apply them to your own pages.
Things not covered in this tutorial:
- Drag and Drop
- Sorting
- Binding
- More Effects
- File Uploads
- Javascript API
- Custom Valiators
- Handlers
84 Conclusion
84.1 Thanks
- Mailing List, Bugs, etc: http://nitrogenproject.com/community
- Follow Nitrogen on Twitter: @nitrogenproject
- Follow Rusty on Twitter: @rustyio
- Follow Jesse on Twitter: @jessegumm