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:
% given this event
#event{ type=click, postback={click, Id} }
% this event function would handle it
event({click, ClickedId}) ->
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.
% delgated event
#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:
-record(silly, {?ELEMENT_BASE(element_silly)}). 
And the module is likewise simple:
-module(element_silly).
-compile(export_all).
-include("elements.hrl").
-include_lib("nitrogen/include/wf.inc").
render(ControlId, R) ->
    Id = wf:temp_id(),
    %% wait!! where do we get the loc from?!
    ClickEvent = #event{type=click, postback={click, Loc}}
    Panel = #panel{id=Id, style="width:'100px' height='100px'",
               actions=ClickEvent}, element_panel:render(Panel).

event({click, Loc}) ->
    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:
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:
Script = "var coord = obj('me').pageX + obj('me').pageY;",
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:
-module(element_silly).
-compile(export_all).
-include("elements.hrl").
-include_lib("nitrogen/include/wf.inc").
render(ControlId, R) ->
    Id = wf:temp_id(),
    DropBoxId = wf:temp_id(),
    MsgId = wf:temp_id(),
    Script = wf:f("var coord = obj('me').pageX + obj('me').pageY; $('~s').value = coord;",
    [DropBoxId]),
    ClickEvent = #event{type=click, postback={click, Id, MsgId},
                        actions=Script},
    Panel = #panel{id=Id, style="width:'100px'; height='100px'",
                   actions=ClickEvent, body=[#hidden{id=DropBoxId},
                                             #panel{id=MsgId}]},
    element_panel:render(Panel).

event({click, Id, Msg}) ->
    Loc = hd(wf:q(Id)),
    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:

Oh my goodness he just updated his blog!!

Published On: 2009-04-05 20:29:18
Hi there! Honestly I'm not dead, I've just been all wrapped up in this whole life thing. You know... that whole, "Holy cow!!!! I work at Google now!! When did this happen exactly?" thing, where you're incredibly busy just trying to get up to speed on it all and catch your breath? Well anyway, I feel bad since I've been silent for so long, so here goes. An update from the trenches of the Life of Jeremy Wall. Since I wrote last I have
  • been "acquired by Google"
  • had my first "truly successful" Open Source Project
  • and actually got my Student Loan back under control.
I'm actually absurdly proud of that last one... And still a bit bewildered by the first one. So lets go down the list one at a time shall we?

"The Acquisition"

The company I worked for DoubleClick Performics got bought by Google. Who would have thought it? Somehow I landed an actual job at Google doing what I love. Crafting Code. I have to say, I think this has to have been a "God thing". I can't see any other way to explain it. Google of course is an awesome place to work. Free food, Smart people, Gameroom (strangely I almost never make into there though), Snacks, and really interesting technology to play with. I'm learning a lot about working in Highly Available, Highly Scalable environments. I keep waiting to wake up and find out it was all a dream.

"The Open Source Project"

My last post was about etap, my learn erlang project. I had no idea that the project would get the attention of Nick, a coder with EA, who was looking for a TAP compliant testing framework for their erlang code. He contacted me and asked if he could take over management of the code. I said" sure", as long as I still got to contribute when I had time. Before I knew it EA was using etap internally and I had what I consider to be my first truly successful open source project. etap has now gotten used commercially, traveled to conferences, and is soon to be featured in a book. Not bad for a learners project huh?

"The School Loan"

And perhaps most awesome of all I'm making headway on the whole credit repair thing. The school loan is back under control and no longer has a devestating impact on my credit report. This is an accomplishment that makes me really wonder if I'm in some kind of awesome dream or something. Life is really looking up around here. I'll try to get more regular on my posting again as I play with more erlang, think about writing a book, and Oh almost forgot to mention "Joose" the meta-object protocol for javascript. I'll write more about that later.

Tags:

Oh my goodness he just updated his blog!!

Published On: 2009-04-05 20:29:18
Hi there! Honestly I'm not dead, I've just been all wrapped up in this whole life thing. You know... that whole, "Holy cow!!!! I work at Google now!! When did this happen exactly?" thing, where you're incredibly busy just trying to get up to speed on it all and catch your breath? Well anyway, I feel bad since I've been silent for so long, so here goes. An update from the trenches of the Life of Jeremy Wall. Since I wrote last I have
  • been "acquired by Google"
  • had my first "truly successful" Open Source Project
  • and actually got my Student Loan back under control.
I'm actually absurdly proud of that last one... And still a bit bewildered by the first one. So lets go down the list one at a time shall we?

"The Acquisition"

The company I worked for DoubleClick Performics got bought by Google. Who would have thought it? Somehow I landed an actual job at Google doing what I love. Crafting Code. I have to say, I think this has to have been a "God thing". I can't see any other way to explain it. Google of course is an awesome place to work. Free food, Smart people, Gameroom (strangely I almost never make into there though), Snacks, and really interesting technology to play with. I'm learning a lot about working in Highly Available, Highly Scalable environments. I keep waiting to wake up and find out it was all a dream.

"The Open Source Project"

My last post was about etap, my learn erlang project. I had no idea that the project would get the attention of Nick, a coder with EA, who was looking for a TAP compliant testing framework for their erlang code. He contacted me and asked if he could take over management of the code. I said" sure", as long as I still got to contribute when I had time. Before I knew it EA was using etap internally and I had what I consider to be my first truly successful open source project. etap has now gotten used commercially, traveled to conferences, and is soon to be featured in a book. Not bad for a learners project huh?

"The School Loan"

And perhaps most awesome of all I'm making headway on the whole credit repair thing. The school loan is back under control and no longer has a devestating impact on my credit report. This is an accomplishment that makes me really wonder if I'm in some kind of awesome dream or something. Life is really looking up around here. I'll try to get more regular on my posting again as I play with more erlang, think about writing a book, and Oh almost forgot to mention "Joose" the meta-object protocol for javascript. I'll write more about that later.

Tags:

Using Reusable AJAX Gateways

Published On: 2005-12-02 15:43:31
So now I have a reusable ajax gateway. Just what exactly am I supposed to do with it? If you look around for a while you will start to notice everyone describing how you can use XSLT, SOAP, and all these other things to pass Objects back and forth. And again they all have suggestions for libraries you can use to do this in. But what if your not quite that ambitious? What if you wanted the speed and power and downright fun of using AJAX without all the huge libraries? Well as usuall I have an idea. You see what I really want to do with this is to retrieve pieces of html pages from the server to put into my current page. Simple enough right? Why I could just use cloneNode from the DOM api to do that. In fact if you looked at my example code from before you saw that I did exactly that. There's just one problem though. The cloned elements and test show up on your page alright but they aren't part of you html document. In fact the element don't obey any of your html rendering engines rules. It's as if you just went about making up fake tags to put in there. They don't do anything. What we need is a way to take our xml document and duplicate it's structure in our html document. duplicate_nodes() to the rescue!!! I wrote a small function that takes our html fragments (as I call them) and duplicates them in our pages document. Here is how I did it: function duplicate_nodes(node) { // get our node type name and list of children // loop through all the nodes and recreate them in our document //alert('calling duplicate_nodes: ' + node.nodeName + ' type: ' + node.nodeType); var newnode; if (node.nodeType == 1) { //alert('element mode'); newnode = document.createElement(node.nodeName); //alert('node added'); newnode.nodeValue = node.nodeValue //test for attributes var attr = node.attributes; var n_attr = attr.length for (i = 0; i < n_attr; i++) { newnode.setAttribute(attr.item(i).name, attr.item(i).nodeValue); alert('added attribute: ' + attr.item(i).name + ' with a value of: ' + attr.item(i).nodeValue); } } else if (node.nodeType == 3 || node.nodeType == 4) { //alert('text mode'); try { newnode = document.createTextNode(node.data); //alert('node added'); } catch(e) { alert('failed adding node'); } } while (node.firstChild){ if (newnode) { //alert('node has children'); var childNode = duplicate_nodes(node.firstChild); //alert ('back from recursive call with:' + childNode.nodeName); newnode.appendChild(childNode); node.removeChild(node.firstChild); } } return newnode; } Now this functions currently only handles elements, their attributes, and text or cdata nodes. entity and other node type support can be added easily however. Also I still need to do some testing on the attribute handling to see if it correctly handles stuff like eventhandlers and id attributes but it works. (Edit: It handles event handlers with no modification on firefox) Lets do like all good code hackers do and take it apart :-) Our first task in this function is to see what kind of node we are handling. This is contained the in the nodeType property of the node object. When this is a 1 it's an element. When it's a 3 or 4 it's CDATA or a Text node. Thus our if statements: if (node.nodeType == 1) { } else if (node.nodeType == 3 || node.nodeType == 4) { } Elements and Text or CDATA have to be handled very differently so we check for these two types before doing anything else. In the case of an element node (type 1) we need two more peices of information: node.nodeName and node.nodeValue These provide us with the details we need when recreating our element in the html document. They are pretty well self explanatory one is the name or tagName of the element and the other is the elements value. Now we are ready to start creating our new element in the current document like so: newnode = document.createElement(node.nodeName); //alert('node added'); newnode.nodeValue = node.nodeValue Now how do we handle it's attributes? A simple for loop will do that for us. the attributes property gives us a list of the nodes attributes. The calling the length property for that list gives us how many attributes there are. And the for loop loops through each one duplicating it in our newnode like so: //test for attributes var attr = node.attributes; var n_attr = attr.length for (i = 0; i < n_attr; i++) { newnode.setAttribute(attr.item(i).name, attr.item(i).nodeValue); alert('added attribute: ' + attr.item(i).name + ' with a value of: ' + attr.item(i).nodeValue); } And that's all we need to recreate our element and its attributes. Text nodes are even easier to handle. you just need one piece of information for them. The data property. create a new text node using the document.createTextNode method with the node.data property and your good to go: //alert('text mode'); try { newnode = document.createTextNode(node.data); //alert('node added'); } catch(e) { alert('failed adding node'); } There is just one last thing to take care of though. What if our node has children? What do you do then? Function Recursion to the rescue!! The firstChild property of a node will tell us if there are any children and a while loop will keep looping as long as it returns true. All we have to do is:
  • call duplicate_nodes recursively with that child as an argument
  • append the returned node to the newnode
  • remove each child from the node
  • and keep looping till no more children exist
Here is the while loop: while (node.firstChild){ if (newnode) { //alert('node has children'); var childNode = duplicate_nodes(node.firstChild); //alert ('back from recursive call with:' + childNode.nodeName); newnode.appendChild(childNode); node.removeChild(node.firstChild); } } The last task of our function is to return the duplicated node return newnode; our duplicate function does not append the node anywhere in our document so it won't show up. That is the job of the calling function. It can append the new node where ever it wants.

Tags:

Reuseable AJAX gateways

Published On: 2005-11-28 16:13:20
Everyone knows about AJAX these days. You just about can't go anywhere on the net whithout hearing about it. And if you're a coder who want's to know more than just what library you should download to start using it you've probably done a little googling and came up with this site: XMLHttpRequest Objects [developer.apple.com] You even played around with the examples and made a few demo apps then realized. Hey!! How can I make these things reusable without ugly global variables and functions that check to see if the response came back yet? In short: how do I use this in a real app? Apple has done a really good job of showing how the xmlhttprequest object works. They even do a good job of showing some useful ways to use it. But if you're like me you want to go a bit farther. I like reusability. I also don't like using Global variables as a gatekeeper. So lets take a look at how we can make this code a little more reusable. The first thing to do is come up with a way to use multiple different functions as the handler for that onreadystate property. Using the same handler really cramps our style. Additionally having to write all that code to test our object's state is a real drag. It would be nice if we could avoid having to write that for every single function we use as a handler. Here is the solution: Let's start with this function here: function loadXMLDoc(url) { req = false; // branch for native XMLHttpRequest object if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } // branch for IE/Windows ActiveX version } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } if(req) { req.onreadystatechange = processReqChange; req.open("GET", url, true); req.send(""); } } Now for this to do what we really need it to we need a couple of different things. That processReqChange function needs to be able to change dynamically. So lets add another function argument that will hold a function passed in to be used here. Like so: loadXMLDoc(url, func) then you can change req.onreadystatechange = processReqChange; to req.onreadystatechange = func; This will allow us to pass any function we want as the state change handler. Don't go deleting that processReqChange function yet though. We still need it. In fact lets take a look at that one right now shall we? function processReqChange() { // only if req shows "loaded" if (req.readyState == 4) { // only if "OK" if (req.status == 200) { // ...processing statements go here... } else { alert("There was a problem retrieving the XML data:\n" + req.statusText); } } } We need this to keep checking our state and tell us when our response came back. We also need it to use any xmlhttprequest object we want it to. What we don't need it to do is retrieve our response for us. In short we need it to recieve a request object in it's arguments and return a response saying it's ok to process our response. So lets modify it a little shall we? function processReqChange(req) { // only if req shows "loaded" if (req.readyState == 4) { // only if "OK" if (req.status == 200) { return 1; // it's safe now go ahead } else { alert("There was a problem retrieving the XML data:\n" + req.statusText); } } return 0; //it's not safe yet } now when we pass this function a request object it returns 1 when we have our response and 0 when the response is not ready yet. Both of these functions are now reusable. But how exactly do we start using them? I thought you would never ask. lets build an example: function append_to_id(el, contents) { var element = document.getElementById(el) ; //alert('appending: ' + contents.nodeValue ); element.appendChild(contents); } function append(url, el) { //alert('starting append operation'); var func = function() { if (processReqChange(req)) { var ajax_return = req.responseXML; while (ajax_return.hasChildNodes()) { append_to_id(el, ajax_return.firstChild); ajax_return.removeChild(ajax_return.firstChild); } } } var req= loadXMLDoc(url, func); } In the append function we create a dynamic function that we can pass to our loadXMLDoc function. That dynamic function contains the meat of what we are wanting to do. It uses an if statement that checks our processReqChange function for a valid return. When it gets a valid return the if statement processes our request. It couldn't be any eaiser. you can see full example code here: Example Script

Tags:

WikiWyg

Published On: 2005-08-26 01:03:28
Wykiwyg With the higher profile, dynamic javascript pages are getting, look to see a lot more folks working on this stuff. And this is a wonderful example of what you can do.

Tags: