Sunday, March 16, 2008

More JavaScript Joy

Before I jump in to the promised $namespace function, let me introduce you to a close friend of mine:
function $panic (msg)
{
if (!console)
alert("PANIC: " + msg);
else
{
console.error("PANIC: " + msg);
if (console.open)
console.open();
}
}

This little function and I have been friends for some time. The Firebug API for logging errors grew on to the basic alert statement many moons ago, and I try to leave a Firebug breakpoint set right inside. Very handy. If you think this only applies to Firefox, you should look at Firebug Lite.

Anyway, here's the $namespace function:
// Forms:
// #1 (ns, {});
// #2 ("...");
// #3 ("...", {});
// #4 (ns, "...");
// #5 (ns, "...", {});
function $namespace ()
{
var sub, ns = arguments[0], pos = 1; // form #1
if (typeof(ns) == "string") // forms #2 & #3
{
sub = ns;
ns = $namespace.global;
}
else if (typeof(arguments[1]) == "string") // forms #4 & #5
{
sub = arguments[1];
pos = 2;
}

if (sub)
{
var parts = sub.split(".");
for (var i = 0; i < parts.length; ++i)
{
var s = parts[i], fn = ns.$namespace.fullname;
if (!ns[s])
ns.$namespace.children.push(ns[s] =
{ $namespace : { children : [],
fullname : (fn ? (fn+".") : "") + s,
name : s, parent : ns } });

ns = ns[s];
}
}

var add = arguments[pos];
if (add)
for (var name in add)
{
var v = add[name];
if (ns.hasOwnProperty(name))
$panic("Namespace "+ns.$namespace.fullname+
" conflict with '"+name+"'");
ns[name] = v;
if (v && v.$namespace)
v.$namespace(ns, name);
}
}

$namespace.children = [];
$namespace.fullname = "";
$namespace.global = function () { return this; }();
$namespace.name = "";
$namespace.parent = null;

There are several points worthy of some discussion in the above code. The first thing beyond the mundane usage form is that all namespaces contains a $namespace property. This property holds a few key pieces of meta-data for the curious. The name and fullname properties are pretty self-explanatory. The parent property stores a link up the hierarchy. The children array contains the list of child namespaces. This can be convenient since child namespaces are stored in their parent namespace alongside all the other stuff (like functions).

I decided to store all this meta-data in a $namespace property for a couple reasons. First, I wanted to reduce the probability of name collisions much as possible. I mean, it is the user's namespace after all. Second, this was symmetric with the presence of $namespace itself in the global namespace. In fact, you can see several properties added to the $namespace function for this purpose.

Something rather cute is the $namespace.global property. That odd little function is one way to reference the global scope. You could ask "why not just use 'window' and be done with it?" The answer is that by doing it this way, this code will work in non-browser contexts (such as the Rhino JavaScript engine). My $panic function is probably browser-specific. That is unless the Java-side were to publish console and/or alert methods, which is a nice way to go if you're using Rhino.

The last item worth talking about is in the loop where members are copied to the new namespace. Other than checking for name collision (a good idea to be sure), there is a detection and delegation step: the loop inspects each object for a $namespace property and, if found, invokes it as a function passing the namespace to which the object is being added as well as the object's name. The reason for this will become clear in a future post.

Well, that's it for now. Enjoy!

No comments: