Sunday, October 10, 2010

Javascript Singleton

I was working on something that required a store of arrays returned from an ajax call; so I came up with the following snippet of code.

I don’t think I words explain it better than the code below.

Here’s the collection program (CollectionFactory.utils.js file):
   1: var CollectionFactory = function () {
   2:     return {
   3:         Collection:new Array(),
   4:         add: function (collection_object) {
   5:             this.Collection.push(collection_object);
   6:             this.triggerEvent("add");
   7:         },
   8:         removeAt:function(index) {
   9:             this.Collection.splice(index, 1);
  10:             this.triggerEvent("remove");
  11:         },
  12:         empty:function() {
  13:             this.Collection = null;
  14:             this.Collection = new Array();
  15:             this.triggerEvent("empty");
  16:         },
  17:         getAt:function (index) {
  18:             return this.Collection[index];    
  19:         },
  20:         replaceAt:function (index, collection_object) {
  21:             this.Collection.splice(index, 1, collection_object);
  22:             this.triggerEvent("replace");
  23:         },
  24:         replaceOrAdd:function (collection_object, compare_key) {
  25:             var replaced = false;
  26:             /* see if anything needs to be replaced first */
  27:             for (var i=0;i<this.Collection.length;i++) {
  28:                 if (this.Collection[i][compare_key] == collection_object[compare_key]) {
  29:                     this.replaceAt(i, collection_object);
  30:                     replaced = true;
  31:                 }
  32:             }
  33:             /* if nothing was replaced, then the item needs to be added */
  34:             if (!replaced) {        
  35:                 this.add(collection_object);
  36:             }
  37:             return;
  38:         },
  39:         
  40:         getType: function () {
  41:             return (typeof this.Collection[0]) 
  42:         },
  43:         triggerEvent:function (method_name) {
  44:             $(document).trigger('COLLECTION_CHANGED', [{methodName:method_name, type:this.getType()}]);
  45:         }
  46:     }
  47: };

[updated with a jQuery event trigger method]


 


A System-User super class (SysUser.model.js file):




   1: SysUser = function () {}
   2: SysUser.prototype.setConfig = function (config) {
   3:     this.first_name = config.first_name;
   4:     this.last_name = config.last_name;
   5:     this.username = (config.username == null || config.username == "" || config.username == undefined)?"guest":config.username;
   6:     this.password = (config.password == null || config.password == "" || config.password == undefined)?"guest":config.password;
   7:     this.updated_date = (config.updated_date == null || config.updated_date == "" || config.updated_date == undefined)?new Date():config.updated_date;
   8:     this.created_date = (config.created_date == null || config.created_date == "" || config.created_date == undefined)?new Date():config.created_date;
   9: }

A Customer class derived from System-User (Customer.model.js file):




   1: var Customer = function () {}
   2: Customer.prototype = new SysUser;
   3: Customer.prototype.constructor = this;
   4: Customer.prototype.parent = SysUser.prototype;
   5: Customer.prototype.setConfig = function (config) {
   6:     this.parent.prototype.setConfig(config); // call to super
   7:     this.customer_id = config.customer_id;
   8:     this.address = config.address;
   9:     this.city = config.city;
  10:     this.state = config.state;
  11:     this.zip = config.zip;
  12: }


And finally the CustomerCollectionFactory singleton implementation that will return new CollectionFactory object (CustomerCollectionFactory.utils.js file)





   1: var CustomerCollectionFactory = function () {
   2:     return new CollectionFactory();
   3: }();

Now to demonstrate the usage; suppose you are making an AJAX call and retreiving a bunch of “customer” objects (I’m assuming JSON return) displaying them in a master-grid and and making edits to them in a dependant form. You need a central store something like an ArrayCollection in Flex with bindings to the Grid and Form.

You’d use the collection as shown in the code below:




   1: // jquery...
   2: $.ajax({
   3:     url:SERVER_URL,
   4:     type:"POST",
   5:     dataType:"json",
   6:     ...
   7:     success:function (data) {
   8:         if (data.result) {
   9:             var config = (data.data[0]); // return from AJAX call is in DATA[0] and is incidentally same format as "config" object expected by Customer's setConfig method
  10:             
  11:             var cust = new Customer()
  12:                 cust.setConfig(config);
  13:             // add or replace
  14:             CustomerCollectionFactory.replaceAt(0, cust);    
  15:             
  16:             // for my example I am guranteed only 1 row, 
  17:             // index is always 0, so I just replace
  18:             // but you could call the add(cust) method here
  19:             ...
  20:             ...
  21:             // do other things 
  22:             ...
  23:         }
  24:         else {
  25:             // do something 
  26:         }
  27:     }
  28: }


Now when you click the “delete” button on your grid all you have to do is:




   1: CustomerCollectionFactory.remove(index_from_grid);

To enable the binding, you can bubble the “collection_changed” event upto the document



   1: $(document).bind('COLLECTION_CHANGED', function (event, properties){
   2:     // check the properties object to see if the event is "SysUserCollectionChanged"
   3:     // do something like:
   4:     $('#MyGrid').jqGrid('addRowData', rowid, SysUserCollectionChanged.Collection[i], position, srcrowid);    
   5:     
   6:     // or
   7:     $('#MyTextBox').val(SysUserCollectionChanged.Collection[i]);
   8:  
   9: });

 



Hope this helps someone….

1 comment:

Akshay Shah said...

I think this is a very useful utility. It makes the developer think in terms of State and has a common place to see the state changes on the objects.

Now the Flex / Silverlight UIs that support 2 way binding automatically see changes and reacts.

Real beauty is this is still pure JS and one does not be sold to SilverLight / FLex / HTML 5 technologies.

I can throw in Phone UIs also.

Server throws just JSON no matter what UI and Object Layer @ UI remains the same.

I do see some sanity in the world of UI insanity.