Ninja JavaScript: (function(){})()   Leave a comment

dlr2008

We interrupt our discussion of structured JavaScript to discuss John Resig’s Ninja approach to Javascript. John’s approach to JavaScript places the accent on

  • Leveraging JQuery; and
  • Minimizing the use of global variables; Using
    • Anonymous Functions
    • Closures; and nest everything in the anonymous construct:
    • (function(){})()

Let’s define a real world problem:
Make an AJAX call to a data source every ‘X’ milliseconds (for the life of the page) and update the page UI on
each AJAX
return. (Think Stock Ticker for example). We will use the JQuery to manage

  • the AJAX call;
  • bind and signal events; and
  • UI manipulation.

We will use the JavaScript function setInterval to manage when the AJAX call is made. Along the way we will use

some JSON to help us manage passing arguments to the (JQUERY based) AJAX call. This combination will produce
some very tight code with a minimum of global variables (well no global functions if we want to).
First Here is the SHELL of the code, then I will present the final code and then break it down in detail.

SHELL: Based On Model: (function(){})()

(
function(pTarget, timeout, rootID, successEvent, failureEvent) {
$(‘#’ + rootID).bind(failureEvent, function(e, jsObject) {});
$(‘#’ + rootID).bind(successEvent, function(e, jsObject) {});
var _T = window.setInterval( function() {//setInterval anonymous function
$.ajax(//JQUERY AJAX Call
{//JQUERY AJAX Arguments
AJAXOPTIONS : values,//in this format
error: function(XMLHttpRequest, textStatus, errorThrown) {},//AJAX error function option
success: function(data) {}//AJAX success function option
}//end of arguments to $.ajax
)//end of ajax call
}//end setInterval anonymous function
, timeout //call this every ‘timeout’ milliseconds
); //end setInterval
function x(){}//local inner, private functions go here
} //end outer anonymous function
)//end of outer parentheses
(‘DatapageSecure.aspx’, 5, ‘KeepAlive’, ‘AICAJAXSUCCESS’, ‘AICAJAXFAILURE’ );//constructor arguments

Shell Discussion

OK. We start with our old friend:

(function(){})()

This time we will not create a return interface and we will not assign the output of this construction to
a (global) variable (or any other namespace).  We try for a pure Ninja approach.  Get in, set up the functionality and leave behind no trace in the JavaScript namespace.

Brief Overview:

  • (function(arguments) {})(arguments);
    • create an anonymous function (a closure) and pass the values of the constructor arguments to this function
    • Passing the (outer) argument list into the anonymous function preserves these fields (and there values) locally within the function closure
  • Now let’s focus on the anonymous function within the parentheses:
    • (function(arguments) {})(arguments)
  • Within this function we set up two event sinks (bind) to be used by the AJAX calls which will fire (trigger) these events on success or failure
    • $(selector).bind(eventName, function(e,argument) {});
    • These event sinks create anonymous functions (closures) to handle the work preformed when an event is caught.
    • See my notes below on how these functions leverage the properties of these closures.
  • Now we set up the repeating calls to the AJAX function using the (standard) JavaScript setInterval function
    • In abstract form call setInterval call looks like:
    • var t=context.setInterval(function(){},timeperiod);
    • See my notes below for additional comments on the setInterval function
  • Note that I am using another anonymous function as the target for the setInterval call.
    • By doing this the function has access, (if it needs them) to all of the local fields of the enclosing anonymous function
    • We are going to place the JQuery AJAX function within the anonymous function called by setInterval.
  • Take a moment to ponder that at this point we have created four anonymous functions
    • One each for the two bind events;
    • An outer anonymous function; and
    • an additional anonymous function nested inside of that.
  • We are going to nest two additional anonymous functions (closures) within.
    • In symbolic form the JQuery AJAX function takes the form:
    • $.Ajax({JSON FORMATTED Options})
    • See my comments below for additional comments on the $.Ajax function.
    • The JSON Formatted options take the form of optional arguments
      • such as type: ‘GET’ which determine the type of call to make (Get vs Post)
    • For our purposes here the most important options to discuss are the two (optional) functions which are called when the AJAX call returns
      • these are not anonymous functions
      • they have names: success and error
      • Since these functions are expressed in JSON format they act as nested closures in their own right
        • With access to any local fields
          • Local to $.Ajax and to the outer anonymous functions in our example.
    • The essential work of these functions (in this design) is to trigger events we have already defined, and
    • to pass data from the AJAX call to these event sinks
    • In symbolic form the success function looks like:
      • success: function(data) {
      • //do any necessary internal work then call the appropriate event
      • $(selector).trigger(event name, argument);
      • }
  • Ok at this point our work is done and all we need to is add the correct number of brackets and parenthesis
  • to close all of this work.
Take a break then come back and look at the full version of the code and my additional usage notes.
Mississippi River Home - New Orleans

Final Code

01            //local variables added to make this code read more clearly for this blog post
02            var rootID = ‘KeepAlive’; //parameter, major UI DIV ID
03            var targetURL = ‘DatapageSecure.aspx’; //parameter
04            var timeoutInSeconds = 1140; //parameter
05            var AJAXSuccessEvent = ‘AICAJAXSUCCESS’; //EVENTNAME
06            var AJAXFailureEvent = ‘AICAJAXFAILURE’; //EVENTNAME
07          (function(pTarget, timeout, rootID, successEvent, failureEvent) {
08              $(‘#’ + rootID).bind(AJAXFailureEvent, function(e, jsObject) {
09                    var tmp=’ Failure. ‘+new Date() +” HTTPRequest Status: “+jsObject.status;
10                    $(this).find(‘#’ + ‘KeepAliveFalure’).html(tmp);
11              });
12                $(‘#’ + rootID).bind(AJAXSuccessEvent, function(e, jsObject) {
13                    var stmp = ‘User: ‘ + jsObject.UserName + ” Calls: ” + jsObject.Calls ;
14                    $(this).find(‘#’ + ‘KeepAliveChat’).html(stmp);
15                });
16                timeout = timeout * 1000; //upgrade to miliseconds
17                var _Hits = 0;
18                var _T = window.setInterval(
19                            function() {
20                                $.ajax(
21                                            {
22                                                url: pTarget,
23                                                type: ‘GET’,
24                                                data: ”,
25                                                dataType: ‘HTML’,
26                                                timeout: timeout / 2,
27                                                error: function(XMLHttpRequest, textStatus, errorThrown) {
28                                                    window.clearInterval(_T);
29                                                    var x={};
30                                                    x[“status”]=XMLHttpRequest.status;
31                                                    x[“textStatus”]=textStatus;
32                                                    $(‘#’ + rootID).trigger(failureEvent,XMLHttpRequest);
33                                                },
34                                                success: function(data) {
35                                                    _Hits++; //for debuging
36                                                    var jsObject = ExtractData(data);
37                                                    jsObject[“Calls”] = _Hits;
38                                                    jsObject[“TimeStamp”] = new Date();
39                                                    $(‘#’ + rootID).trigger(successEvent, jsObject);
40                                                }
41                                            }//end of arguments to $.ajax
42                                        )//end of ajax call
43                            } //end setInterval anonymous function
44                              , timeout); //end setInterval
45              function ExtractData(data){
46                  //code omited as not relavent to blog post
47                  }//end of local function
48            } //end outer anonymous function
49                )(targetURL, timeoutInSeconds, rootID, AJAXSuccessEvent, AJAXFailureEvent);   //DO IT

Notes On Specific Features

Binding events:

1.     $(‘#’ + rootID).bind(failureEvent, function(e, jsObject) {
2.     var tmp=' Failure. '+tmp1 +" HTTPRequest Status: "+jsObject.status;
3.     $(this).find('#KeepAliveFalure').html(tmp);
4.     });

Line 1: JQuery is built up using selectors which simulate CSS selectors. Here $(‘#’+rootID) seeks within the DOM all
HTML elements with an ID equal to the value of the variable ‘rootID’. The “bind” JQuery verb binds an event (here a
custom event with a name contained in the variable ‘failureEvent’) with an anonymous function:
function(e, jsObject){}. JQuery normalizes the binding of events between major browsers so we do not need to
concern our code with what vendors agent we are running within. The bind verb will pass two arguments from
the signal event: ‘e’ which is the standard event object and any optional arguments provided by the signaler. In this
case the second argument is a JavaScript object with a (user defined) set of properties.
Line 3: This is a very nice example of terse JQuery script. The selector: $(this) refers to whatever was defined as the
selector in Line 1. The ‘find’ verb applies a selector (in this case looking for the ID ‘KeepAliveFalure) within the children
of the primary selector (in this case ‘this’). The ‘ html’ verb replaces the innerHTML of the find selector with the value of the variable ‘tmp’.
This is tight, no?

Calling AJAX with JQUERY


JQuery defines several ways to make AJAX calls I am using one of these ( $.ajax) here. The structure of this call is:
Line 1. $.ajax(
Line 2. {}
Line 3. );
Line 2 is of course the meat it is a JSON formatted argument list containing, in our case:
Line 1. url: pTarget, //the url to call
Line 2. type: 'GET', //call type
Line 3. data: '', //Query String Arguments to pass to the target URL
Line 4. dataType: 'HTML', //return datatype
Line 5. timeout: timeout / 2,//how long to wait for a response
Line 6. error: function(XMLHttpRequest, textStatus, errorThrown) {...},
Line 7. success: function(data) {...}
Note: Line 4 defines the type of data returned from the AJAX call. In this case use HTML since we do not want any post
Processing of the data to occur . We could use the key word ‘JSON’ if we expect a JSON response and we want
JQUERY to hydrate the JSON string back into a JavaScript object prior to passing the data to the success anonymous function.
Line 6 defines an anonymous function to be called when the call times out or if the call returns an HTTP code => 400.
JQuery defines and handles the three arguments passed to the error function.
Line 7 defines an anonymous function to be called on a successful AJAX call and the argument ‘data’ is the
actual text returned (subject to the constraint imposed in Line 4).

Processing the AJAX Return

Let’s look at the anonymous success function (given as Line 6 above). In the actual code example we are using
Line 1. success: function(data) {
Line 2. _Hits++; //for debuging
Line 3. var jsObject = ExtractData(data)
Line 4. jsObject["Calls"] = _Hits;
Line 5. jsObject["TimeStamp"] = new Date();
Line 6. $('#' + rootID).trigger(successEvent, jsObject);
Line 8. }
Line 2. ” _Hits” is a local variable defined in the full code example. This is a local variable. Local to the closure of the outer
Anonymous function. It is visible there and to both the success and error anonymous functions but is not visible globally.
I am using it here as a simple debugging device to count the number of the AJAX calls made.

Line 3 takes us Outside of the scope of the current blog and is used to extract some of the data from the return stream and
to create a Javascript object to hold that data. In the working code it is defined as a local non-anonymous function The code has been omitted from our example code since this takes us way outside of the scope of this blog.
Lines 4 and 5 define additional properties of jsObject (used for debugging in our example).
Finally, in Line 6, we use JQUERY to trigger an event and pass the locally created JavaScript object to it. Some things to note about this
Very terse line of code:

  • $(‘#’+rootID) The JQuery selector for the DOM element to trigger to event against. AKA the event sink. See the section on Binding Event Above.
  • .trigger The JQuery trigger event verb, this is a browser neutral event trigger
  • successEvent A variable which contains the name of the event being triggered.
  • jsObject The JavaScript Object containing the local data to pass to the event sink.

Setting Up the setInterval Function Call

The JavaScript function “setInterval” controls calling a function repeatedly every “X” milliseconds. Its form is:
var _T = owner.setInterval(function(){},timeout);
where:
owner is the context which ‘owns’ the setInterval
_T is the handle for the setInterval.
Function(){} is the anonymous function to call; and
Timeout is the milliseconds between invocations of the anonymous function.
Once launched, setInterval will continue to be called for the life of the page or until a call is made to cancel the setInterval cycle:
owner.clearInterval(_T);
Note: the handle (_T here) returned by the setInterval function is a simple number if we have
Var _T a.setInterval(function(){},timeout);
and
b.clearInterval(_T);
the original setInterval will not be cleared and no error is generated with the call.
One could write
var _T = setInterval(function(){},timeout);
and
clearInterval(_T);
and HOPE that the context is the same for each call. But better to assure success by specifying the context of both
the setInterval and the clearInterval call. In our example code we assign each call to the “window” context:
var _T = window.setInterval(function(){},timeout);
and
window.clearInterval(_T);
so our context and intent is clear.

So What’s The Down Side?

We get some very compact code here with NO side effects and a very minimal global namespace impact when we use John’s Ninja approach. The downside is the syntax is very complex and very fragile. A typo anywhere in the code is likely to get you a general syntax error message, generally in the last line of the (function(){})(); construct. Not very helpful that. We are mixing standard JavaScript syntax with JSON syntax together and it is easy to get lost without good planning. Personally we never make syntax errors, but for others this may be a problem! An additional downside is debugging anonymous functions by setting break points in your running code will not work. I work in a team environment and when I move this solution into production, I will likely break out some of the anonymous functions into normal (inner functions) to make it easier for other team members see the intent of the code, to ease making code changes and to simplify debugging. If I was shipping code to be worked on only by myself or by a (very) limited sized team I would leave it just as it and watch it fly.

Home

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: