donderdag 18 februari 2010

Some stuff I miss in javascript

I haven't go a lot of time, so this will be a short one.

There is a lot of stuff I miss in javascript. I made an small lib for myself, which I want to share.

First I added some OO patterns:

Decorator,
Proxy,
Observer,
Observable,
Strategy,
Chain,
Chainable exceptionhandlers

I added a logger, which plays nicely with firebug. And some functional stuff like:

foldr,
foldl

A function, which returns a function, which allow partial function application:

pfunc

And a function, which gives any object an fluent interface (useful for loggers):

fluentize

And also added the function merge to the Array object. Here is the small lib:

There are probably some bugs and I haven't documentation, but on request I can add some examples.

/* Some functions I miss in javascript */


/* Haskell style partial function application */

/* 
Transforms a function of type:
        ( (a, b) -> c) 
to 
        a -> b -> c 
so if we have a function
f(x,y,z) -> x - y + z
we can do this now:
f(1,y,z) = g(y,z) = 1 - y + z
or this:
f(1,2,z) = g(z) = 1 - 2 + z
or this 
f(x,y,z) = f(x,y,z);

use this class as 
function min(a,b,c){
 return a - b + c;
}
var pmin = pfunc(min);
var t = pmin(1)(2)(3);
print(t);


*/

function pfunc(func, i, acc){
        if(!i){ i = 0; acc = new Array(); }
        if(i == func.arity) return func.apply(func, acc);

        return function(){
                acc.merge(arguments);
                return pfunc(func, i + arguments.length, acc);
        }
}
/* Makes an object fluent */

function fluentize(obj){
 var newobj = {};
 for(key in obj){
  var val = obj[key];
  var valn;
  if(typeof(val) == 'function'){
   newobj[key] = function(){
    val.apply(obj, this.arguments); 
    return obj;
   }

  } else {

   newobj[key] = val;
  }
 }
 return newobj;
}
/* Foldr and fold */

function foldr(list, func, acc){
 var list = list.reverse();
        if(list.length == 0) return acc;
 for(var i = 0; i < list.length; i++){
  var val = list[i];
  if(typeof(val) != 'string' && typeof(val) != 'boolean' && typeof(val) != 'number') continue;
  acc = func(val,acc);
 }
 return acc;
}
function foldl(list, func, acc){
        if(list.length == 0) return acc;
 for(var i = 0; i < list.length; i++){
  var val = list[i];
  if(typeof(val) != 'string' && typeof(val) != 'boolean' && typeof(val) != 'number') continue;
  acc = func(acc, val);
 }
 return acc;
}

/* Very important, array merge: */

Array.prototype.merge = function(arr){
        for(var i = 0; i < arr.length; i++){
                this.push(arr[i]);
        }
}
/*  Voor ieder een hel
 Voor de enkeling een hemel
 maak je keuze snel

*/
/* 
 Function which can ensure an element exists. 
 callback should be return true or false
*/

function ensure(callback, cps){
 function _ensurethis(){
  if(callback()){
   cps();
   return;
  }
  ensure(callback, cps);
 }
 window.setTimeout(_ensurethis, 10);
}

/* Some OO patterns, which are very useful: */

/* Extends an object with other objects 
* @function extendedObject = Extend(obj, obj1, obj2, obj3 .. objn)
*/

function Extend(){
        var obj = arguments[0];
        for(var i = 1; i < arguments.length; i++){
                for(key in arguments[i]){
                        obj[key] = arguments[i][key];
                }
        }
        return obj;
}
/* Couple of methods, which let us forward calls to an inner object,
        very useful to build decorators, proxies etcetera */
function ForwardCall(){
}

/* Forward calls to an inner object */

ForwardCall.prototype.forwardCall = function(func) {
        this[func] = function(){
                return this.innerCall(func, arguments);
        }
}
ForwardCall.prototype.innerCall = function (func, args){
        return this.obj[func].apply(this.obj, args);
}

/* Decorator object */

Decorator.prototype = ForwardCall.prototype;

function Decorator(obj){
 this.obj = obj;
 for(key in this.obj){
  var val = this.obj[key];
  if(typeof(val) == 'function'){
   this.forwardCall(key);

  }
 }
}

/* Proxy object */

Proxy.prototype = ForwardCall.prototype;

function Proxy(obj){
 this.obj = obj;
 for(key in obj){
  var val = this.obj[key];
  if(typeof(val) == 'function'){
   this[key] = function(){
    var args = this.before(key, arguments);
    ret = this.innerCall(key, args);
    return this.after(key, ret);
   }
  }
 }
 this.before = function(func, args){
  return args;
 };
 this.after = function (func, ret){
  return ret;
 }
}

/* Observable */

function Observable(){
}
Observable.prototype.addObserver = function(observer){
 if(typeof(this.observers) == 'undefined'){ 
                this.observers = new Array(); 
        } 
        this.observers.push(observer); 
} 
Observable.prototype.updateObservers = function (message){ 
        for(observerid in this.observers){ 
                var observer = this.observers[observerid]; 
                observer.receiveMessage(message); 
        } 
} 
 
/* Observer */ 
 
function Observer(){ 
 
} 
 
Observer.prototype.receiveMessage = function (message){ 
         
} 
 
/* Implementation of the strategy pattern */ 
 
function Strategy(){ 
 this.strategies = new Array(); 
 this.createStrategy = function (func, test){
  return {func: func, test: test};
 };
 this.addStrategy = function (func, test){
  this.strategies.push(this.createStrategy(func, test));
  return this.strategies.length;
 }; 
 this.setDefaultStrategy = function (func){ 
  this.defaultStrategy = func; 
 }; 
 this.removeStrategy = function (id){ 
  this.strategies[id] = null; 
 }; 
 this.run = function() { 
  var args = arguments; 
  for(key in this.strategies){ 
   if(this.strategies[key] != null){ 
    var strategy = this.strategies[key]; 
    if(strategy['test'].apply(this, args)){ 
     return strategy['func'].apply(this, args); 
    } 
   } 
  } 
  return this.defaultStrategy.apply(this, args);   
 } 

} 
 
function Chain(){ 
} 
function Chain(test, func){ 
 this.func = func; 
 this.test = test; 
 this.next = null; 
 this.run = function(){ 
  if( this.test.apply(this, arguments) ){ 
   return this.func.apply(this, arguments); 
  } 
  if(this.next != null){
   return this.next.run.apply(this.next, arguments);
  }
  return null;
 }
 this.addToChain = function(test,func){
  if(this.next == null){
   this.next = new Chain(test,func);       
   return;
  }
  this.next.addToChain(test,func);
 }
}

ExceptionHandler.prototype = ForwardCall.prototype;
function ExceptionHandler (obj){
 this.obj = obj;
 this.strategy = new Strategy;
 this.strategy.setDefaultStrategy(function(ex,key){
   print ("Uncaught Exception calling: " + key);
   print(ex);
   return;
   });

 this.addFunc = function(key){
  this[key] = function(){
   try {
    this.innerCall(key, arguments);
   } catch (e){
    this.strategy.run(e, key);
   }
  }
 }

 for(key in this.obj){
  var val = this.obj[key];
  if(typeof(val) == 'function'){
   this.addFunc(key);
    return this.next.run.apply(this.next, arguments);
  }
  return null;
 }
 this.addToChain = function(test,func){
  if(this.next == null){
   this.next = new Chain(test,func);       
   return;
  }
  this.next.addToChain(test,func);
 }
}

ExceptionHandler.prototype = ForwardCall.prototype;
function ExceptionHandler (obj){
 this.obj = obj;
 this.strategy = new Strategy;
 this.strategy.setDefaultStrategy(function(ex,key){
   print ("Uncaught Exception calling: " + key);
   print(ex);
   return;
   });

 this.addFunc = function(key){
  this[key] = function(){
   try {
    this.innerCall(key, arguments);
   } catch (e){
    this.strategy.run(e, key);
   }
  }
 }

 for(key in this.obj){
  var val = this.obj[key];
  if(typeof(val) == 'function'){
   this.addFunc(key);
    this.addFunc(key);
  }               
 }                       
 this.addHandler = function(test, func){
  this.strategy.addStrategy(test, func);
 }
}


/* Inspection function */
function Inspect(obj, depth, i, indent){
        if(!depth){
                depth = 5;
        }
        if(!i){
                i = 0;
                indent = '';
        }
        for(key in obj){
                var type = typeof(obj[key]);
                print (indent + type  + ": " + key);
                if((type == 'object' || type == 'function' ) && i < depth){
                        Inspect(obj[key], depth, i + 1, indent + "  ");
                }
        }

}
/* We need to define a print function, to work with all the classes above: 
*/
function print(str){
 Logger.getInstance().startGroup('print').log('from: ' + arguments.callee.caller.toString()).log(str).endGroup();
}

/* HTML generating functions */

/* Simple positioning function */
function putHTML(pos){
 return function(data){
  
  Logger.getInstance().startGroup('putHTML');
  Logger.getInstance().log('position data at: ' + pos);
  $(pos).html(data);
  Logger.getInstance().endGroup(); 
 }
}
/* Javascript logger, logs to firebug, this an singleton, on non development site, we have to run Logger.getInstance().setSilence(),
then it shuts up */
function Logger(){
 this.silence = false;
}

Logger.getInstance = function(){
 if(!Logger.instance){
  Logger.instance = /*fluentize(*/new Logger();//);
 }
 return Logger.instance;
}
Logger.prototype.setSilence = function(silence){
 if(!silence){
  silence = true;
 }
 this.silence = silence;
}
Logger.prototype.startGroup = function(name){
 if(this.silence) return;
 console.group(name);
 return this;
}
Logger.prototype.endGroup = function(){
 if(this.silence) return;
 console.groupEnd();
 return this;
}
Logger.prototype.log = function (logline, errorlevel){
 /* formatted mode */
 if(!console || this.silence){
  return;
 }
 if(typeof(logline) == 'object'){
  this.multiline(logline, errorlevel);
 }
 if(errorlevel == 'error'){
  console.error(logline);
 } else 
 if(errorlevel == 'warn'){
  console.warn(logline);
 } else {
  console.info(logline);
 }
 return this;
}
Logger.prototype.multiline = function(loglines, errorlevel){
 console.group('Object inspector:');
 console.dir(loglines);
 console.groupEnd();
 return this;
}