Marzhill Musings

Advanced Nitrogen Elements

Published On: 2009-06-16 23:33:58
In my last post I walked you through creating a basic nitrogen element. In this one I'll be covering some of the more advanced topics in nitrogen elements.
  • Event handlers and delegation
  • scripts and dynamic javascript postbacks

Nitrogen Event handlers

Nitrogen event handlers get called for any nitrogen event. A nitrogen event is specified by assigning #event to an actions attribute of a nitrogen element. The event handler in the pages module will get called with the postback of the event. Postbacks are an attribute of the event record and define the event that was fired. To handle the event you create an event function in the target module that matches your postback. For Example: 1 2 % given this event 3 #event{ type=click, postback={click, Id} } 4 % this event function would handle it 5 event({click, ClickedId}) -> 6 io:format("I [~p] was clicked", [ClickedId]) . Erlangs pattern matching makes it especially well suited for this kind of event based programming. The one annoying limitation of this event though is that each page has to handle it individually. You could of course create a dispatching module that handled the event for you but why when nitrogen already did it for you. You can delegate an event to a specific module by setting the delegate attribute to the atom identifying that module. 1 2 % delgated event 3 #event{ type=click, postback={click, Id}, delegate=my_module } You can delgate to any module you want. I use the general rule of thumb that if the event affects other elements on the page then the page module should probably handle it. If, however, the event doesn't affect other elements on the page then the element's module can handle it.

Scripts and Dynamic Postback

Now lets get make it a little more interesting. Imagine a scenario where we want to interact with some javascript on a page and dynamically generate data to send back to nitrogen. As an example lets create a silly element that grabs the mouse coordinates of a click on the element and sends that back to nitrogen. A first attempt might look something like so: 1 2 -record(silly, {?ELEMENT_BASE(element_silly)}). And the module is likewise simple: 1 2 -module(element_silly). 3 -compile(export_all). 4 -include("elements.hrl"). 5 -include_lib("nitrogen/include/wf.inc"). 6 render(ControlId, R) -> 7 Id = wf:temp_id(), 8 %% wait!! where do we get the loc from?! 9 ClickEvent = #event{type=click, postback={click, Loc}} 10 Panel = #panel{id=Id, style="width:'100px' height='100px'", 11 actions=ClickEvent}, element_panel:render(Panel). 12 13 event({click, Loc}) -> 14 wf:update(body, wf:f("you clicked at point: ~s", Loc)). Well of course you spot the problem here. Since the click happens client side we don't know what to put in the Loc variable for the postback. A typical postback won't work because the data will be generated in the client and not the Nitrogen server. So how could we get the value of the coordinates sent back? The javascript to grab the coordinates with jquery looks like this: 1 2 var coord = obj('me').pageX + obj('me').pageY; To plug that in to the click event is pretty easy since action fields in an event can hold other events or javascript or a list combining both: 1 2 Script = "var coord = obj('me').pageX + obj('me').pageY;", 3 ClickEvent = #event{type=click, postback={click, Loc}, actions=Script} Now we've managed to capture the coordinates of the mouse click, but we still haven't sent it back to the server. This javascript needs a little help. What we need is a drop box. Lets enhance our element with a few helpers: 1 2 -module(element_silly). 3 -compile(export_all). 4 -include("elements.hrl"). 5 -include_lib("nitrogen/include/wf.inc"). 6 render(ControlId, R) -> 7 Id = wf:temp_id(), 8 DropBoxId = wf:temp_id(), 9 MsgId = wf:temp_id(), 10 Script = wf:f("var coord = obj('me').pageX + obj('me').pageY; $('~s').value = coord;", 11 [DropBoxId]), 12 ClickEvent = #event{type=click, postback={click, Id, MsgId}, 13 actions=Script}, 14 Panel = #panel{id=Id, style="width:'100px'; height='100px'", 15 actions=ClickEvent, body=[#hidden{id=DropBoxId}, 16 #panel{id=MsgId}]}, 17 element_panel:render(Panel). 18 19 event({click, Id, Msg}) -> 20 Loc = hd(wf:q(Id)), 21 wf:update(Msg, wf:f("you clicked at point: ~s", Loc)). Ahhh there we go. Now our element when clicked will:
  1. use javascript to grab the coordinates of the mouse click
  2. use javascript to store those coordinates in the hidden element
  3. use a postback to send the click event back to a nitrogen event handler with the id of the hidden element where it stored the coordinates.
We have managed to grab dynamically generated data from the client side and drop it somehwere that nitrogen can retrieve it. In the process we have used an event handler, custom javascript, and dynamic javascript postbacks. Edit: Corrected typo - June 16, 2009 at 11:40 pm

Tags: