How to enforce execution order of javascript actions in Nitrogen – take 2

I already blogged about one way to enforce execution order of javascript actions in Nitrogen. Initially I used javascript custom events which are supported in new specification for HTML 5.

But, as it turned out, there is a much cleaner way to archive the same effect by using jquery API tigger() method.

Here is the discussion on custom event on Stackoverflow.

slightly modified example from Stackoverflow shows the essence of trigger() method:

$("p").bind("myCustomEvent", function(e, myName, myValue){alert('custom event triggered')});
    $("button").click(function () {

the cool part is that it allows to bind to custom event in the same way as you would bind to any other jquery event, with bind() or on() methods, and this makes it work transparently with Nitrogen.

Nitrogen event model is built around event{} action. See action_event.erl for implementation. This is a generic way to build javascript actions which are rendered on the html page.
Internally, is calls Nitrogen.$observe_event – a method defined in Nitrogen.js which is calling jQuery bind() method. It also calls Nitrogen.$queue_event – a method which allows to send an event back to your erlang page (it basically calls jquery.ajax method which sends a Post back to the webserver).

Anyway, the fact that event{} uses bind() method, allows for strightforwad use of jquery custom events.

I will be giving explanation for the scenario I needed for jqgrid element implementation for Nitrogen_Elements.

here are the possible steps:

I needed a reliable way to get notified when jqgrid element was initialized:

in element_jqgrid.erl which implements the control, I first call javascript which inits the control and immediately call jQuery trigger method with a name of custom event – ‘jqgrid_init’.

 %% create grid
    wf:wire(ID, wf:f("$(function(){$(obj('~s')).jqGrid(~s);})", [ID, Options])),
    %% fire jquery custom event to mark the completion of jsgrid init
    wf:wire(ID, wf:f("$(obj('~s')).trigger('jqgrid_init')", [ID])),

then, on the page which outputs the instance of jqgrid{}, I add the jqgrid{} record with parameter for my grid, including the events I want to catch when they happen for jqgrid. For my example I want to catch ONCELLSELECT and ONSELECTROW events:

	    id = jqgrid,
		{url, 'get_jqgrid_data'},
		{datatype, <<"json">>},
		{colNames, ['ID', 'Name', 'Values']},
		{colModel, [
		    [{name, 'id'}, {index, 'id'}, {width, 55}],
		    [{name, 'name'}, {index, 'name1'}, {width, 80}],
		    [{name, 'values1'}, {index, 'values1'}, {width, 100}]
		{rowNum, 10},
		{rowList, [10, 20, 30]},
		{sortname, 'id'},
		{viewrecords, true},
		{sortorder, <<"desc">>},
		{caption, <<"JSON Example">>}
	    actions = [
		#jqgrid_event{trigger = jqgrid, target = jqgrid, type = ?ONSELECTROW},
		#jqgrid_event{trigger = jqgrid, target = jqgrid, type = ?ONCELLSELECT}

The code which implements #jqgrid_event{} is in action_jqgrid.erl module and it looks like this:


-define(JQGRID_ELEMENT, #jqgrid{}).

render_action(#jqgrid_event{trigger = Trigger, target = Target, type = ?ONSELECTROW}) ->
    PickledPostbackInfo = wf_event:serialize_event_context(?ONSELECTROW, Target, Target, ?JQGRID_ELEMENT#jqgrid.module),
    #event{target = Target, type = 'jqgrid_init', actions =
	       wf:f("jQuery(obj('~s')).jqGrid('setGridParam', {~s: function(rowid, status, e) {
              Nitrogen.$queue_event('~s', '~s', \"&rowid=\" + rowid + \"&status=\" + status);}})",
	 [Target, ?ONSELECTROW, Target, PickledPostbackInfo])};
render_action(#jqgrid_event{trigger = Trigger, target = Target, type = ?ONCELLSELECT}) ->
    PickledPostbackInfo = wf_event:serialize_event_context(?ONCELLSELECT, Target, Target, ?JQGRID_ELEMENT#jqgrid.module),
    #event{target = Target, type = 'jqgrid_init', actions =
	       wf:f("jQuery(obj('~s')).jqGrid('setGridParam', {~s: function(rowid, status, e) {
                    Nitrogen.$queue_event('~s', '~s', \"&rowid=\" + rowid);}})",
		    [Target, ?ONCELLSELECT, Target, PickledPostbackInfo])}.

for each jqgrid event type (e.g. ONCELLSELECT or ONSELECTROW ) we first call Nitrogen method wf_event:serialize_event_context – this prepares Postback structure.

Then, we use standard #event{} action and bind to our custom event ‘jqgrid_init’ and define an action which we want to perform when this event happens. In this case we execute a piece of logic which adds event handler function to jqgrid instance for ONCELLSELECT or ONSELECTROW event.
For this we call Nitrogen.$queue_event function which fires a http POST (via jquery.ajax method) back to webserver, where we receive it in event function in element_jqgrid.erl module.

As the result of all of this we trigger jqgrid_init event as soon as the jqgrid is ready, and bind to its ONCELLSELECT and ONSELECTROW events. This allows to ensure that binding only happens when jqgrid control is ready.

I think jquery custom events add very nice extra functionality to Nitrogen without having to change anything in existing Nitrogen APIs and allow to sort out the nasty issue with javascript execution order in relatively generic way.

Jesse Gumm is also working on a new Nitrogen API (priority-queue based event wiring) which will fix the execution order problem but meanwhile jquery custom events might help as well.

The code for this blog entry is available here:

Nitrogen Elements Examples

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: Logo

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