jscroft posted on October 13, 2007 02:57

I'm in the process of writing an AJAX-based web application that—small surprise—requires a fair amount of processing on both the client and the server. My object model is complex, and on the server side I've simplified things by making heavy use of enumerated types. Ideally, I'd like to use a lighter-weight version of the same object model on the client side, but I immediately ran into a problem: JavaScript offers no native support for enumerated types!
I did some poking around, in the process learning a great deal about Object-Oriented Programming (OOP) in JavaScript. It turns out that OOP is fairly painless in JavaScript once you understand the techniques—Ray Djajadinata's article on MSDN is indispensable—but enumerated types are one thing he didn't really address, and nobody else has really managed to make them as painless as it seems they ought to be. All the tools are there, though, and with a little bit of thought, it turned out to be a remarkably easy problem to solve.
On the server side, declaring and instantiating an enumerated type looks something like this:
C#
namespace A.B
{
public enum Fruit
{
Apple,
Banana,
Cherry,
DavidHasselhoff
}
}
A.B.Fruit myFruit = A.B.Fruit.Apple;
Let's make a couple of observations:
- Namespaces matter. There is a strong argument to be made that one should never present code without embedding it in a namespace, in order to prevent name collisions. This is doubly true in JavaScript—where namespaces are still rare and collisions are thus more likely—and trebly true in a modular environment like DotNetNuke where a module developer can (by design!) have no idea what might be running one module over on the same page. Any robust enum implementation has got to support namespaces!
- An enum declaration looks a lot like a class declaration, doesn't it? No constructor, but since every enum is really an int under the hood, why would you need one?
- Let's take it one step farther: how is an enum really any different from an int? Answer: it has a bunch of static properties, one for each enumerated value!
Let's put these observations together and reconstitute our enum accordingly:
C#
namespace A.B
{
public class Fruit
{
static public int Apple = 0;
static public int Banana = 1;
static public int Cherry = 2;
static public int DavidHasselhoff = 3;
private int _Value;
public int Value
{
get { return _Value; }
set { _Value = value; }
}
}
}
A.B.Fruit myFruit = A.B.Fruit.Apple;
A.B.Fruit yourFruit = new A.B.Fruit(A.B.Fruit.DavidHasselhoff);
Pretty ugly, isn't it?
This becomes a useful exercise, though, when you match it up with the concepts Djajadinata presents in his article on JavaScript OOP, referenced above. Let's apply those concepts and translate the Fruit class into JavaScript:
JavaScript
var A = A ? A : new Object();
A.B = A.B ? A.B : new Object();
A.B.Fruit = function(_Value)
{
this.getValue = function() { return _Value; };
this.setValue = function(val) { _Value = val; };
}
A.B.Fruit.Apple = 0;
A.B.Fruit.Banana = 1;
A.B.Fruit.Cherry = 2;
A.B.Fruit.DavidHasselhoff = 3;
var myFruit = new A.B.Fruit();
myFruit.setValue(A.B.Fruit.Apple);
var yourFruit = new A.B.Fruit(A.B.Fruit.DavidHasselhoff);
Easy, right?
That's a joke. The instantiation is easy—it's only marginally more complex than the C# version—but the Javascript declaration is a nightmare! What we need is a helper function that can generate the appropriate namespace & class declaration behind the scenes. Then our "declaration" would be reduced to a function call, and really would be easy.
Here's one way to do it:
JavaScript
function Enum()
{
var e = function(_Value)
{
this.getValue = function() { return _Value; };
this.setValue = function(val) { _Value = val; };
}
for (var i = 1; i < arguments.length; i++)
e[arguments]i[] = i - 1;
var path = arguments[0].split(".");
var parent = window;
while (path.length > 1)
{
child = path.shift();
if (parent[child] == undefined) parent[child] = new Object();
parent = parent[child];
}
parent[path] = e;
}
Now it's easy to declare and instantiate an arbitrary enumerated type! Here are the declaration and instantiation that correspond to the Fruit type:
JavaScript
Enum(
"A.B.Fruit",
"Apple",
"Banana",
"Cherry",
"DavidHasselhoff"
)
var myFruit = new A.B.Fruit();
myFruit.setValue(A.B.Fruit.Apple);
var yourFruit = new A.B.Fruit(A.B.Fruit.DavidHasselhoff);
To recap, what we've done is to declare a helper function, Enum(), that uses a simple syntax to generate an arbitrary enumerated type behind the scenes as a true object-oriented JavaScript class. Once declared, you can treat the resulting enum like any other JavaScript object class.