Monday, December 3, 2007

Javascript : Simulating namespaces thru prototyping part 2

In the first part, we have seen how we could simulate namespaces very basically with one function used as a container.
We have seen that namespaces allow to create clean clode, that do not interfere or polluate the global space too much.
We will see here how to extend the namespace simulation thru a simple Namespace function

Namespace thru prototype


We have seen that a simple function used as a namespace could give us a lot of advantages but the drawback was that the main function,used as a namespace, wrapped all the other functions.
And so what?
It won't have too much overhead if this function is used only once in your application, but if you 'instantiate' the 'class' several times and that it contains hundreds of line, these hundreds of lines will be kept in memory several times.
In order to avoid this, we are going to use the prototype keyword.
Every functions get a 'prototype' attached to them to which you can add further functions.
Let's see the example we used in the first entry to see what's going on :
function Arrays() {
    this.toArray = function(elm){
         var tmpArray=[];
         for(var i=0;i < elm.length;i++){
            tmpArray.push(elm[i]);
         }
         return tmpArray;
    },
    this.search = function(elm,search) {
          for(var i=0;i < elm.length;i++){
              if(elm[i]===search) return i;
          }
          return false;
    }
}
Arrays = new Arrays();
alert(Arrays.toSource());#won't work in IE.
The above code create a 'class' containing all the functions.
We then instantiate an object and see what it contains thru the toSource function.
Executing this code, you will see that everything defined within the function Arrays, used as a namespace, will be present in the object.
If you were to create several instances, each instance we hold the entire computation.
Even if you only instantiate it once,you may not need the search function though.
Here comes the prototype!
The prototype allows us to attach functions afterwards.
Let's see how it works :
function Arrays() {
    this.toArray = function(elm){
         var tmpArray=[];
         for(var i=0;i < elm.length;i++){
            tmpArray.push(elm[i]);
         }
         return tmpArray;
    }
}
oArrays= new Arrays();
alert(oArrays.toSource());

Arrays.prototype = {
    search : function(elm,search) {
          for(var i=0;i < elm.length;i++){
              if(elm[i]===search) return i;
          }
          return false;
    }
};

alert(oArrays.toSource());
alert(oArrays.search(['bonjour','aurevoir','hi'],'hi')); 
#error, function search not defined
If you execute the code, you will see that the instance oArrays of Arrays only contains the toArray function and not search function.
You also see that adding the search function to the prototype of the Arrays function doesn't change what is contained in the oArrays instance.
But on the other hand, you see that the oArrays instance doesn't contain the search function at all and will throw an error when trying to call it!

How work the prototype?

In the above code, we have done the thing in this order:
1) define the namespace Arrays with one function
2) create an instance of the Arrays 'class' called oArrays
3) Add a new object containing the definition of the search function to the prototype
of Arrays
4) call the search function on the oArrays instance
There is one thing to remember about the prototype keyword and instantiation:
only defined prototype functions will be available to the instance.
In other word, if you call the Arrays function,create an instance of it and then define a new prototype to it, the prototype won't be available to your instance, here oArrays.
Here is how we should have done to make it available to the oArrays object:
1) define the namespace Arrays with one function
3) Add a new object containing the definition of the search function to the prototype
of Arrays
2) create an instance of the Arrays 'class' called oArrays
4) call the search function on the oArrays instance
And now, you will be able to use the search function!
Why ? Basically, your program is going to be analized in a top-bottom process.
Nothing really different form other languages! But everything happens at run-time, which means that when you instantiate the class, the object will contain the information only available at that time!(only the code that has been parsed- simplified explanation).
So let's rewrite the code so that we can access what we need:
function Arrays() {
    this.toArray = function(elm){
         var tmpArray=[];
         for(var i=0;i < elm.length;i++){
            tmpArray.push(elm[i]);
         }
         return tmpArray;
    }
}
oArrays= new Arrays();
alert(oArrays.toSource());

Arrays.prototype = {
    search : function(elm,search) {
          for(var i=0;i < elm.length;i++){
              if(elm[i]===search) return i;
          }
          return false;
    }
};
oArrays2= new Arrays();#re-instantiate the class here!
alert(oArrays2.toSource()); #same result as oArrays, just the toArray definition
alert(oArrays2.search(['bonjour','aurevoir','hi'],'hi')); #return 2

As you can see by creating a new object, oArrays2, after the prototype definition, you will be able to use the search function!
Even more!
You see that the instance, oArrays and oArrays2 just hold the toArray function!
In fact, by default, when calling a function on an instance (call a method in that case), javascript will try to see if it can find the definition within the prototype and goes back to all the prototype chain if necessary!
Thanks to the prototype chain, we can now create a simple empty namespace, ie a function and then attach the functions thru the prototype!
You will be then able to load only what you really need!

How to use namespaces : Choosing the root


In javascript, there is no real standard defining all the libraries and namespaces in use (like CPAN for perl - we will talk about jsan later).
In order to be sure that your functions and libraries are not going to polluate the global space too much , or enter in conflict with an other library, you should place everthing into a base namespace.
This namespace can be seen as the root of the tree or as the root of folders on your hard drive or an id, like an IP address.
We have been developping an animation function that had many helper functions in the global namespace.
We will then use the namespace concept to clean this out and play nice with other libraries!
The hardest thing to do is to find the root name, it should be very unique and we should make it not too evident!
Alright, we are going to use JAME, which is a name that I have found right now, so don't be to hard on me!
This stands for : Javascript Animations Made Easy
As you can see, this is not a too evident name like Animation, Tween...
As far as you are sure that your root namespace is not too evident, then you can always do:
JAME.Util.Strings
JAME.Util.Arrays
These are evident names, almost standards, but they are all attached to the JAME root namespace that protects them!

The Root Namespace by hand


We have seen that JAME is going to be the root of our namespace tree that is why it is not necessary to have any real definitions into it, we should see it as a place holder.
We can create the namespace very easily then:
JAME = function() {};
And here we are!
But we also know that we will need to extend the JAME namespace thru its prototype and not directly as we want to be able to load only what we need.
Therefore, we shoud do it this way:
JAME = function() {};
JAME = JAME.prototype=function () {};
Now any elements add to JAME will be added to the prototype.
We want to create an other namespace within JAME that will contain all the helper functions we've been using so far.
Therefore,we will add the Util namespace, that is an other place older.
So we just create an empty namespace here too.
JAME.Util = function() {};
JAME.Util = JAME.Util.prototype= function() {};
And now we have the JAME.Util namespace.
But as you can see this is quite a lot of typo!
We are going to create a function that will deal with creating namespaces for us.

The Namespace function


Before we define the function, we will see how to use it:
Namespace("JAME.Util.Strings");
As you can see this is pretty straightforward!
We just write a string containing each namespace seperated by a dot.
Let's see how to create a one depth namespacing function, that is just going to wrap what we have done above:
function Namespace(name) {
    window[name]=function() {};
    window[name]=window[name].prototype=function() {};
}
So now you can do:
Namespace("JAME");
JAME.Util=function() {};
We just put the name onto the window object and then create the shortcut to work directly with the prototype of the name.
If we follow the above namespace, in order to add a namespace of depth 2, we should write:
function Namespace(name1,name2) {
    window[name1]=function() {};
    window[name1]=window[name1].prototype=function() {};
    window[name1][name2]=function () {};
    window[name1][name2]=window[name1][name2].prototype=function() {};
}
you can see that we can add the depth thru [][].
If we had 3 elements, it will be [][][].
Let's create a general function that will deal with n depth elements.

function Namespace (sName) {

 var namespaces = sName.split('.') || [sName];
 var nlen = namespaces.length;
 var root = window;
 var F = function() {};

 for(var i=0; i < nlen; i++) {
  var ns = namespaces[i];
  if(typeof(root[ns])==='undefined') {
   root = root[ns] = F;
   root = root.prototype = F;
  }
  else
   root = root[ns];
 }
}
What's going on?
First we split our string by dots or put the sole name in an array.
Then we create an alias to the window object.
Then we loop thru each namespaces.
We first check that the namespace doesn't already exist.
If not, we assign F to the root and then assign the prototype to the root again.
If we do it step by step:
First loop root = window so:
root = window[namespace[0]] = F; window[root] = root.prototype = F; Then second loop:
root = root[namespace[1]] = F; window[root] = root.prototype = F; ...
And now you can create namespaces very easily!
Namespace("JAME.Util.Strings");
Namespace("JAME.Util.Colors");
Namespace("JAME.Util.Numbers");
Let's see all the helper functions we have seen so far:
function camelize(str) {
  return str.replace(/-(.)/g, function(m, l){return l.toUpperCase()});
}
function hyphenize (str) {
  return str.replace(/([A-Z])/g, function(m, l){return '-'+l.toLowerCase()});
}
function firstToUpperCase(str) {
  return str.replace(/^([a-z])/, function(m, l){return l.toUpperCase()});
 }
function trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
}
These are all the helper functions related to strings we've been using so far to create the animation functions.
We are now, just going to put this into the JAME.Util.Strings namespace :
//in the Util folder, we add Strings.js 

//we define the namespace
Namespace("JAME.Util.Strings");


JAME.Util.Strings={
camelize: function(str) {
   return str.replace(/-(.)/g, function(m, l){return l.toUpperCase()});
},
hyphenize : function(str) {
  return str.replace(/([A-Z])/g, function(m, l){return '-'+l.toLowerCase()});
},
firstToUpperCase : function(str) {
  return str.replace(/^([a-z])/, function(m, l){return l.toUpperCase()});
},
trim : function(str) {
   return str.replace(/^\s+|\s+$/g, '');
}
};
Now let's define the colors and numbers:
//in the Util folder, we add Numbers.js 

//we define the namespace
Namespace("JAME.Util.Numbers");

JAME.Util.Number= {
    d2h : function(dec) { 
       return dec.toString(16);
    },
    h2d :function(hex) { 
       return parseInt(hex,16);
   },
   randomize : function(min,max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
   }
};
And the colors:
Package('JAME.Util.Colors');

JAME.Util.Colors = {
rgb2h:function (r,g,b) { 
    return [JAME.Util.Numbers.d2h(r),JAME.Util.Numbers.d2h(g),JAME.Util.Numbers.d2h(b)];
},
h2rgb:function (h,e,x) {  
     return [JAME.Util.Numbers.h2d(h),JAME.Util.Numbers.h2d(e),JAME.Util.Numbers.h2d(x)];
},
cssColor2rgb:function (color) {
if(color.indexOf('rgb')<=-1) {
   return this.hexStr2rgbArray(color);
}
   return this.rgbStr2rgbArray(color);
},
hexStr2rgbArray:function (color) {
return this.h2rgb(color.substring(1,3),color.substring(3,5),color.substring(5,7));
},
rgbStr2rgbArray:function (color) {
return color.substring(4,color.length-1).split(',');
}

};
We are not going to see them all for now but I guess you got the picture!
Now you can use them this way:
JAME.Util.Strings.trim(str);
So you may think, and you are right, that it is a lot of typo!
Indeed it is but you should not decide for the end user what to put in the global space, the end user can do it very easily:
window["trim"]=JAME.Util.Strings.trim;
trim(str);
The user can give it an other namespace if he/she wants!
It's up to the context they're in!
You should avoid functions/variables in the global namespace.
If you use the global namespace, be sure to make a list available to the end user!

Conclusion


We have seen how to create a very basic Namespace function (note namespace and package are reserved words so you can choose either Namespace or Package and be sure it will work).
This allow us to keep our code clean, structured and if we think of each namespaces as being folders as we have been doing above, we will be able to automaticly create a javascript file containing only the contents you have choosen!
We can import everything in the global namespace or rename the namespace very easily but we need to offer a tool to import namespaces.
Inheritance within the namespace hierarchy is not handled yet.

We will see in the next entry how to create an Import function that we'll allow to import functions by controlling what's going on(what if you have split in Arrays and split in Strings and that you import both in the global namespace??)

No comments: