Introduction to Nitrogen

Table of Contents

1 Welcome


Introduction to Nitrogen
A step-by-step introduction to the
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 or bin/dev compile start the sync 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 to index.erl. Then compile and reload. What happens?
  • Add ?PRINT(node()) to index.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)

17 Dynamic Routes Explained

17.1 Nitrogen Pages

17.1.1 Dynamic Routing Explained

Dynamic routing rules:

  1. 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
    
  2. Root page maps to index.erl
  3. Replaces slashes with underscores.
    http://localhost:8000/routes/to/a/module ->
    routes_to_a_module.erl
    
  4. Try the longest matching module.
    http://localhost:8000/routes/to/a/module/foo/bar ->
    routes_to_a_module.erl
    
  5. Modules that aren't found go to web_404.erl if it exists.
  6. 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?

  1. User hits a URL.
  2. URL is mapped to a module.
  3. Nitrogen framework calls module:main()
  4. module:main() calls a #template
  5. #template calls back into the page (or other modules)
  6. 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() to page: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:

  1. Allow you to generate HTML within Erlang:
    • Avoid mixing languages == clearer code.
    • Fewer characters to type.
    • Checked at compile time.
  2. 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

  1. Elements make HTML.
  2. Actions make Javascript.
  3. An action can be wired using the actions property, or wired later with wf:wire/N. Both approaches can take a single action or a list of actions.
  4. An action looks for trigger and target properties. These can be specified in a few different ways.
  5. 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.

ElementShortcut 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 and manager roles, but not the administrator 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} if trap_exit is true.)

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:

  1. A record containing the properties of the element.
  2. A reflect() function, providing a programattic way to get the properties of an element. If record_info(fields, RecordType) worked, this would not be necessary.)
  3. 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

Date: 2013-10-03 13:41:03 CDT

Author: Jesse

Org version 7.8.02 with Emacs version 23

Validate XHTML 1.0

Comments

Note:To specify code blocks, just use the generic code block syntax:
<pre><code>your code here</code></pre>


comments powered by Disqus