Search

What the Quote?

"Cheese harness? That's something everyone needs."

Tim Tripcony

"Duck is the new monkey."

Greg Rotz

"I think the Pope resembles a jack-o-lantern. He's just got the kind of face you wanna carve into a pumpkin."

Pete Oberlin

« the newest item on my XPages wishlist | Main| not particularly helpful »

handy function for namespacing your scoped objects in server-side JavaScript

Category xpages
Java has always organized groups of classes into namespaces called "packages", collections of related classes from a single organizational author. Typically, a package name starts with the author organization's primary web domain in reverse order (i.e. "com.ibm") and ends with a period-delimited hierarchy describing the purpose of the classes the package contains (i.e. "com.ibm.designer.domino.xsp.editor"). This structure assists maintenance programmers in determining the exact purpose - and origin - of any given class, even if the application is comprised of an enormous amount of classes.

As web applications have grown increasingly complex over the past few years, it's become increasingly common for client-side JavaScript objects to be namespaced in a similar manner, though the source prefix is nearly always omitted (Yahoo is a notable exception to this: every web application they deploy now contains a main application object, which is always called "YAHOO.ApplicationName"). It's fairly common, therefore, for JavaScript frameworks to include a function to turn a period-delimited string (i.e. "alt.wesley.crusher") into an object hierarchy (i.e. {alt: {wesley: {crusher: {}}}}). This allows individual members to be added deep into a hierarchy without having to first manually verify that the full hierarchy exists.

The one area of XPages where I can foresee this becoming useful is with the scoped variables, which allow you to store object pointers in one of four different scopes:
  • applicationScope: shared among all users of a given NSF
  • sessionScope: shared across all requests within a single user session
  • viewScope: shared across all requests within a single page - for example, if a user opens home.xsp, and their interaction with the page triggers some partial refresh events, each event has access to the same viewScope as the initial page load; navigating to another page or reloading the current page destroys the viewScope and creates a new one
  • requestScope: shared across all controls rendered for a single request - for example, if a partial refresh event impacts multiple controls (i.e. several inputText controls within a single form panel), all controls within that request share the same requestScope... but do not have access to the requestScope from the initial page load; that request is already complete, so any objects stored during that request no longer exist
Each of these is evaluated as a Java HashMap (or some descendant thereof). This is similar to LotusScript's List or JavaScript's associative array, with one key difference: they're not limited to strings for their keys. You can use dates, numbers, even complex objects as the key by which the data is stored and retrieved. Most often, however, you'll use strings anyway, but don't forget that you don't have to.

As handy as these are, the more complex your application and the greater your need to maximize performance, the more stuff you'll be cramming into one or more of these scopes. Which is fine; assuming your server has enough RAM, there's no danger of them running out of space. The real danger is the same as any variable naming: forgetting that you've already stored something by the same name and clobbering it with new data in the same container. Hence, I can foresee value in defining a namespace hierarchy for the keys used to store scope data.

Officially, the correct way to store and retrieve scope data is by using, respectively, put and get:

currentValue = sessionScope.get("something");
sessionScope.put("something", newValue);

Predictably, however, these behave similarly to the "extended class syntax" in the LotusScript NotesDocument class. Just as you really should always use .getItemValue() and .replaceItemValue() to "get" and "put" document item values, but upon occasion will choose to use doc.Subject to refer to a nonexistent Subject property of the NotesDocument class knowing that it will guess correctly that what you really want is the Subject item on the document to which doc is an object handle, the following variation on the above example will work:

currentValue = sessionScope.something;
sessionScope.something = newValue;

Officially, that's wrong. Furthermore, it's complete nonsense: sessionScope is a HashMap; the HashMap class doesn't have a public member named "something". But... because it's actually JavaScript we're writing that gets turned into Java objects, and JavaScript allows members to be added at any time via dot syntax, the above is perfectly valid JavaScript even though in Java it wouldn't even compile. Bear in mind that IBM prefers that we use get and put because there are some type fidelity issues with the dot syntax approach, but the latter is used all over the place in the Discussion template, so it must not be too terribly dangerous.

So here's a function you can add to a server-side JavaScript library that will allow you to do the same type of namespacing I described earlier for your scope variables:

var scopeNameSpace = function (packageString, targetScope) {
    var currentSpace = targetScope;
    var newSpace;
    var newSpaceName;
    var nameSpaces = packageString.split('.');
    for (var i = 0; i < nameSpaces.length; i++) {
        newSpace = new java.util.HashMap();
        newSpaceName = nameSpaces[i];
        if (!(currentSpace)) {
            // targetScope is undefined, default to viewScope
            currentSpace = viewScope;
        }
        if (!(currentSpace.containsKey(newSpaceName))) {
            currentSpace.put(newSpaceName, newSpace);
        }
        currentSpace = currentSpace.get(newSpaceName);
    }
    return currentSpace;
    /*
    Example usage:
    scopeNameSpace('alt.wesley.crusher', sessionScope);
    sessionScope.alt.wesley.crusher.age = 36;
    --OR--
    scopeNameSpace('alt.wesley.crusher', sessionScope).age = 36;
    */

};


Comments

Gravatar Image1 - Hmm interesting, direct access would certainly make things easier and more javascript like, but I could see where problems could arise.

Also I wonder what happens with put if you try something like sessionScope.put('put', 'whoops') since it is directly exposing those properties.

Gravatar Image2 - @Rich - amazingly, that doesn't seem to break anything. Here's what I tried:

viewScope.put('put', 'oops');
viewScope.put('displayText', viewScope.get('put'));
scopeNameSpace('alt.wesley.crusher');

A computed field bound to viewScope.displayText rendered 'oops'. I changed the second line to:

viewScope.put('displayText', viewScope.put);

The computed field then rendered:

{displayText=(this Map), alt={wesley={crusher={}}}, put=oops}

Apparently they foresaw the problem and put some sanity checks in.

Gravatar Image3 - Would have figured they'd think about it ahead of time. Glad to see you can't break it that easily Emoticon

Gravatar Image4 - Can you reference applicationScope from a server-side JavaScript library? I can't seem to get it to work. How can you create fx. an applicationScope.com.acme.myfunction function within a server-side JavaScript library using your scopeNameSpace(...) inside this library?

Gravatar Image5 - @Andrew - I tested the following and it works:

scopeNameSpace('com.acme', applicationScope).myFunction = function(parameters) {function body};

You can then call the function by its full namespace:

applicationScope.com.acme.myFunction();

Primarily, though, the scope variables seem to be intended for storing data (i.e. results from database queries) to avoid having to retrieve the same data on each page load. Any function you would want stored in the application scope (implying it's available to all users) would probably be better defined in a script library. For example:

var com = {
acme: {
myFunction: function (parameters) {function body}
}
}

This way you can call the function via com.acme.myFunction() from anywhere that loads the library without having to actually store the function object in RAM until the next reboot, which is essentially what the applicationScope does.

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)

Contact Me

Elsewhere

Assorted Linkage


Locations of visitors to this page