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; }