How to enforce execution order of javascript actions in Nitrogen

Nitrogen is still very young and there are certain weaknesses which one starts discovering after initial introduction.

The problem I hit recently is related to the order of execution of javascript actions which Nitrogen outputs to the page.

The problem is the following: you might want to wire several javascript actions to the custom control and sometimes you *must* ensure that they are executed in specific order. E.g. for the tabs example *any* action you want to apply to the instance of tabs control has to run only *after* jQuery tabs initialization function is executed.

so, you can’t select a tab:

 wf:wire(tabs, #tab_select{tab = 2}),

before you completed tabs init:

wf:wire(ID, wf:f("jQuery(obj('~s')).tabs(~s)", [ID, Options]))

The thing is in Nitrogen there is currently no way to specify the order in which javascript functions are added to the page and they might end up on the page in the wrong order.

A number of people discovered this issue for different use cases, you can find one of the discussions on Nitrogen mailing list here.

I think one of the potential ways to get around this problem is to use javascript custom events and delegates which are specified in HTML5 and available for most modern browsers.

Here is the blog post which gives a great explanation how to use custom events.

We can use this idea to guarantee the order of javascript execution for our tabs control example.

First, we need to change tabs init function and create a custom javascript event of type ‘Event’. We will fire up the ?EVENT_TABS_INIT_COMPLETED event as soon as tabs control has been initiaized:

    %% init jQuery tabs control with specified options
    %% fire up event "tabs_init_completed" on completion
    Options = action_jquery_effect:options_to_js(Record#tabs.options),
    wf:wire(ID, wf:f("$(function(){jQuery(obj('~s')).tabs(~s);
                          var evt = document.createEvent('Event');
                          evt.initEvent('~s', true, true);
                          document.dispatchEvent(evt);})", [ID, Options, ?EVENT_TABS_INIT_COMPLETED])),

Secondly, we need to add a small Macro which will bind all the events which are defined for our tabs control to the eventListener for event ?EVENT_TABS_INIT_COMPLETED. This macro will create an eventHandler which will take a function we want to execute as a parameter:

%% a wrapper for javascript custom event - used to make sure that function we want to run is not executed
%% *before* tabs control is initialized
-define(EVENT_LISTENER(EventName, Func), wf:f("document.addEventListener('~s', eventHandler, false);
            function eventHandler(e){ ~s }", [EventName, Func])).

We can now use this macro in action_tabs_methods.erl module which specifies which actions could be used for out custom tabs element:

render_action(#tab_select{target = Target, tab = Index}) ->
    ?EVENT_LISTENER(?EVENT_TABS_INIT_COMPLETED, wf:f("jQuery(obj('~s')).tabs('select', ~w);", [Target, Index]));

After that, we can safely wire, for example #tab_select event and be sure that it is executed *after* tabs control becomes available.

body() ->
    %% now select tab 2 to show that action works 
    wf:wire(tabs, #tab_select{tab = 2}),
	    id = tabs,
            %% select tab 0 at init
	    options=[{selected, 0}],
		#tab{title="Tab 1", url = "/content/tabs2.htm"},
		#tab{title="Tab 2", body=["Tab two body..."]},
		#tab{title="Tab 3", body=["Tab three body..."]}

see full example in Element_tabs and in Nitrogen Elements Examples

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

Leave a Reply

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

You are commenting using your 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