How to build Progressbar custom element with Nitrogen

I will continue showing examples how to build custom elements for Nitrogen Web Framework. You can find another one in this post. This time we will look at implementation of “Progressbar” control. Addition of this element is on TODO list for Nitrogen development, so we might as well do it now.

Progressbar is designed to display the current percent of completed process.

Screen Shot 2013-01-12 at 14.33.21

Just as in the case with tabs control it doesn’t make sense to re-invent the wheel, Progressbar is already available for us in JQuery UI library which is coming with Nitrogen distribution. So, all we need to do is to expose it by writing a simple erlang wrapper for JQuery Progressbar control.

We will need to do three main things :

  • Implement render_element function in element_progressbar.erl module which outputs a valid html markup which JQuery knows how to turn into into progressbar control.
  • Output javascript function which will init progressbar when a html page loads.
  • Implement action_progressbar.erl module which outputs additional javascript actions which we want to make available for our control implementation – e.g. ability to show / hide control or bind to native javascript events: “progressbarcomplete” which fires when progress bar reaches 100% or “progressbarchange”, which fires when control state changes.

We will add Progressbar element into the collection of Nitrogen_Elements, so if you want to follow the code, you can get clone the repo:

git clone git@github.com:RomanShestakov/nitrogen_elements.git

and look at the code in src/progressbar

First, lets add records for the new element and its actions into include/nitrogen_elements.hrl:

%% progress bar
-record(progressbar, {?ELEMENT_BASE(element_progressbar), options=[]}).
-record(progressbar_value, {?ACTION_BASE(action_progressbar), value}).
-record(progressbar_disable, {?ACTION_BASE(action_progressbar)}).
-record(progressbar_enable, {?ACTION_BASE(action_progressbar)}).
-record(progressbar_event_on, {?ACTION_BASE(action_progressbar), event, postback}).
-record(progressbar_event_off, {?ACTION_BASE(action_progressbar), event}).

Then, we add implementation of control in module element_progressbar.erl – this is the module Nitrogen will use to render control, when it finds the record #progressbar{} on the page

-module(element_progressbar).
-include("nitrogen_elements.hrl").
-include_lib("nitrogen_core/include/wf.hrl").
-compile(export_all).

reflect() -> record_info(fields, progressbar).

render_element(Record) ->
    ID = Record#progressbar.id,
    HtmlID = wf:temp_id(),

    %% init jQuery progressbar control with specified options
    Options = action_jquery_effect:options_to_js(Record#progressbar.options),
    wf:wire(ID, wf:f("$(function(){jQuery(obj('~s')).progressbar(~s);})", [ID, Options])),

    %% create html markup
    #panel{id = ID, html_id = HtmlID, style = Record#progressbar.style, class = Record#progressbar.class}.

event(Event) ->
    ?PRINT({progressbar_event, Event}),
    Module = wf:page_module(),
    Module:event(Event).

we add html div element which is represented by #panel{} Nitrogen element and assign html_id to it. We also wire javascript action to turn div element into jQuery progressbar:

    wf:wire(ID, wf:f("$(function(){jQuery(obj('~s')).progressbar(~s);})", [ID, Options])),

Next, we add action_progressbar.erl module, which contains implementations for render_action function – these are custom events which we will be able to wire with wf:wire function to instances of progressbar control.

-module(action_progressbar).
-include("nitrogen_elements.hrl").
-compile(export_all).

-define(PROGRESSBAR_ELEMENT, #progressbar{}).
render_action(#progressbar_value{target = Target, value = Value}) ->
    wf:f("jQuery(obj('~s')).progressbar({value : ~w});", [Target, Value]);
render_action(#progressbar_disable{target = Target}) ->
    wf:f("jQuery(obj('~s')).progressbar({disabled, true);", [Target]);
render_action(#progressbar_enable{target = Target}) ->
    wf:f("jQuery(obj('~s')).progressbar({disabled, false);", [Target]);
render_action(#progressbar_event_on{target = Target, event = Event, postback = Postback}) ->
    #event{type = Event, postback = Postback, delegate = ?PROGRESSBAR_ELEMENT#progressbar.module};
render_action(#progressbar_event_off{target = Target, event = Event}) ->
    wf:f("jQuery(obj('~s')).unbind('~s');", [Target, Event]).

we have javascript functions to show/hide element, set value to it (current process progress percent) and also bind / un-bind to the events which are available for JQuery Progressbar.

This is interesting part as if we bind to, for example, “progressbarchange” event – we will get a postback from the control into the event function of element_progressbar.erl module, which will call the event function of the module with the instance of progressbar control.

This is all for implementation part, now lets look at the control use example. We will create a simple page with “Password” box control and “Progressbar” control. Every time we type a new character into password box, progressbar will show the status.

You can check this JQuery video tutorial to see how to build this example with pure JQuery UI Progressbar (this is *not* my tutorial). We will implement the same example using Nitrogen and our newly developed control.

First, lets create a module progressbar.erl which will contains all the code to render html page:

-module(progressbar).

-include_lib("nitrogen_elements/include/nitrogen_elements.hrl").
-compile(export_all).

main() -> #template{file=filename:join([web_common:templates(), "onecolumn.html"])}.

title() -> "Progressbar Example".
headline() -> "Progressbar Example".

body() ->

    %% wire event with postback
    wf:wire(password_1, #event{type = keyup, postback = {password_1, keyup}}),
    %% wire event for progressbarcomplete and progressbarchange
    wf:wire(progressbar_1, #progressbar_event_on{event = progressbarcomplete, postback = {progressbar_1, complete}}),
    wf:wire(progressbar_1, #progressbar_event_on{event = progressbarchange, postback = {progressbar_1, changed}}),

    %% output html markup
    [
	%% set of controls for test progressbar with postback
	#panel{body = [
	    #label { text="Example for password box with postback: " },
	    #p{},
	    #label { text="Password Box: " },
	    #password {id = password_1, text="" },
	    #p{},
	    #progressbar{ id = progressbar_1, style = "width:230px; height:5px;", options=[{value, 0}]}
	]}
    ].

event({ID, keyup}) ->
    %% get current values from password box with id = password_1
    Len = length(wf:q(password_1)),
    ?PRINT({password_length, Len}),
    wf:wire(progressbar_1, #progressbar_value{value = Len * 10});
event({ID, complete}) ->
    ?PRINT({progressbarcomplete, ID});
event({ID, changed}) ->
    ?PRINT({progressbarchange, ID});
event(Event) ->
    ?PRINT({Event}).

here we are adding a panel with elements for a password box and progressbar element. We also want to wire the following javascript events:

    wf:wire(password_1, #event{type = keyup, postback = {password_1, keyup}}),
    wf:wire(progressbar_1, #progressbar_event_on{event = progressbarcomplete, postback = {progressbar_1, 
    wf:wire(progressbar_1, #progressbar_event_on{event = progressbarchange, postback = {progressbar_1, changed}}),

the fist one will send a postback {password_1, keyup} everytime we type in password box – we can catch this postback in event function and update progressbar status.

Two other events will allow us to get notifications when progressbar status changes and when we reach 100%. I use these two event for demonstration purposes only.

Now, with this code added to the page, we can point the browser to localhost:8000 and see the following:

Screen Shot 2013-01-12 at 17.07.13

Notice, that the page sends a POST back to the webserver everytime we type into password box. This is how Nitrogen is communicating with Erlang code on the server side when you use ‘postback’ option of #event{}.

We might not necessary want this – after all talking to the server every time you press a button might be expensive.

Alternative solution would be to wire an action to the password element which directly updates progressbar element:

    wf:wire(password, #event{type=keyup, trigger = password,
	actions = [wf:f("$(function(){var len = jQuery(obj('~s')).val().length;
                                      jQuery(obj('~s')).progressbar({value : len * 10});
                                      })", [password, progressbar])]}),

here, we listen to ‘keyup’ event from element with id = password (trigger = password) and then set a value of element progressbar. We first check the value of password element and then set value to progressbar. In this case you have the same effect as in the first example but without expensive postbacks sent to the server – this might be preferred solution.

The full example can be found here.

This entry was posted in custom Nitrogen elements, Erlang, Nitrogen and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s