Strongly typed collections with JavaScript and Microsoft Ajax?
Not exactly, but we can achieve a similar result by using the parameter validation mechanism provided by Microsoft Ajax.
Introduction
The core library defines Function._validateParams(parameters, descriptors), that is a function responsible for validating a list of parameters passed as an argument. The second argument is an array of parameter descriptors:
[ param1_descriptor, param2_descriptor, ... ]
Each descriptor is a dictionary (in JavaScript, a dictionary is a set of name:value pairs separated by a comma and enclosed in {}) used to provide information about the expected parameter. If one of the parameters doesn't match the corresponding descriptor, the function raises an exception of type Sys.ArgumentTypeException.
Scenario
Let's see how we can take advantage of this mechanism to create an array that accepts only instances of a particular class. We want to build a CustomerCollection class that holds a list of Customer objects and allows to add and remove them, in a manner similar to a strongly typed collection in the .NET framework.
The Customer class
Our Customer class is pretty simple. It contains only a fullName field and a property to access it:
| Type.registerNamespace('Samples'); |
|
| Samples.Customer = function() { |
| this._fullName; |
| } |
| Samples.Customer.prototype = { |
| set_fullName : function(value) { |
| this._fullName = value; |
| }, |
| |
| get_fullName : function() { |
| return this._fullName; |
| } |
| } |
| Samples.Customer.registerClass('Samples.Customer'); |
Implementing the CustomerCollection class
The CustomerCollection class holds a _innerList field that is the private list of Customers. The access to the inner list happens through the addCustomer() and removeCustomer() methods. Both these methods accept an instance of the Customer class as an argument.
Since JavaScript doesn't perform any check on the type or number of the parameters passed to a method, we are invoking a method called _validateCustomer() inside the body of both addCustomer and removeCustomer.
| Samples.CustomerCollection = function() { |
| this._innerList = []; |
| } |
| Samples.CustomerCollection.prototype = { |
| addCustomer : function(customer) { |
| this._validateCustomer(arguments); |
| |
| Array.add(this._innerList, customer); |
| }, |
| |
| removeCustomer : function(customer) { |
| this._validateCustomer(arguments); |
| |
| Array.remove(this._innerList, customer); |
| }, |
| |
| get_count : function() { |
| return this._innerList.length; |
| }, |
| |
| _validateCustomer : function(args) { |
| var e = Function._validateParams(args, |
| [{name: 'customer', type: Samples.Customer, mayBeNull: false, isOptional: false}]); |
| |
| if(e) throw e; |
| } |
| } |
| Samples.CustomerCollection.registerClass('Samples.CustomerCollection'); |
Let's take a closer look at _validateCustomer. This function takes the list of parameters to validate and matches them with the corresponding descriptors.
The first thing to notice is that, both in addCustomer and removeCustomer, we are passing arguments to _validateCustomer. The arguments reference is created by JavaScript and holds the list of the parameters passed to a method. In our example, arguments holds the customer parameter.
As a consequence, _validateCustomer receives the customer parameter and calls _validateParams with the array of parameter descriptors:
| [{name: 'customer', type: Samples.Customer, mayBeNull: false, isOptional: false}] |
This array contains one descriptor because we are validating one parameter. As you can see, a descriptor is a dictionary that holds the "description" of the parameter: this parameter is associated a "customer" name (just for identification purposes), it must be of type Samples.Customer, it can't be null (mayBeNull) and it can't be omitted (isOptional).
If the given parameter doesn't match the descriptor, the variable e holds an exception that will be thrown in the if statement.
Testing the bits
The following is a simple snippet that tests the CustomerCollection class:
| function pageLoad() { |
| var c1 = new Samples.Customer(); |
| c1.set_fullName('John Doe'); |
| |
| var c2 = new Samples.Customer(); |
| c2.set_fullName('John Smith'); |
| |
| var coll = new Samples.CustomerCollection(); |
| coll.addCustomer(c1); |
|
| coll.addCustomer("blah"); // This will throw. |
| |
| coll.removeCustomer(c1); |
| alert(coll.get_count()); |
| } |