API Docs for: 0.1.0
Show:

File: src/zerk/zerk.js

/*
 * Zerk Game Engine
 * 
 * Copyright (C) 2012 - 2013 by Kristoph Junge
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of 
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to 
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
 * of the Software, and to permit persons to whom the Software is furnished to do 
 * so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 */

/*
 * TODO Turn Zerk main object internal members into private scope
 */

/**
 * Zerk Object
 * 
 * The Zerk main object and namespace
 * 
 * @class zerk
 * @module zerk
 * @static
 **/
var zerk={
	
	/**
	 * The game engine configuration
	 * 
	 * @property _config
	 * @type Object
	 * @private
	 **/
	_config: null,
	
	/**
	 * Load state of classes
	 * 
	 * @property _classState
	 * @type Object
	 * @private
	 **/
	_classState: {},
	
	/**
	 * Class name to instance map
	 * 
	 * @property _classMap
	 * @type Object
	 * @private
	 **/
	_classMap: {},
	
	/**
	 * Class name to parent class map
	 * 
	 * @property _parentClassMap
	 * @type Object
	 * @private
	 **/
	_parentClassMap: {},
	
	/**
	 * Defines that got delayed by requires
	 * 
	 * @property _delayedDefine
	 * @type Object
	 * @private
	 **/
	_delayedDefine: {},
	
	/**
	 * The main bootstrap method
	 * 
	 * @method init
	 * @param {Object} config Configuration object
	 **/
	init: function(config) {
		
		this._config={
			bootstrap: {
				log: {
					enabled: true,
					severity: 0, // 0 = All
					groupFilter: null,
					wrapErrors: true
				}
			}
		};
		
		zerk.apply(this._config,config);
		
		this._initErrorHandler();
		
		this._loadScript('./'+this._config.bootstrap.gameDir+'/class/game.js');
		
	},
	
	/**
	 * Fires when all classes ever required are loaded and defined
	 * 
	 * @method _onLoad
	 * @private
	 **/
	_onLoad: function() {
		
		/*
		 * TODO Implement a clean crossbrowser onready handler
		 */
		
		if (document.readyState==="complete") {
			
			this._ready();
			
		} else {
			
			var self=this;
			window.addEventListener(
				'load',
				function() {
					
					self._ready();
					
				},
				false
			);
			
		}
		
	},
	
	/**
	 * Fires when all classes ever required are loaded and defined
	 * and the document is ready
	 * 
	 * @method _ready
	 * @private
	 **/
	_ready: function() {
		
		var config=this._config;
		var self=this;
		
		window.setTimeout(
			function() {
				
				self.game=zerk.create(
					config.bootstrap.game+'.game',
					config
				);
				
			},
			100
		);
		
	},
	
	/*--------------------------------------------------------------------------
	 | Class System
	 *------------------------------------------------------------------------*/
	
	/**
	 * Defines a new class
	 * 
	 *	// Class definition without meta object
	 *	zerk.define('myNamespace.myClass',{
	 *		// ...
	 *	}); 
	 * 
	 *	// Class definition with meta object
	 *	zerk.define({
	 *		name: 'myNamespace.myClass',
	 *		extend: 'myNamespace.myParentClass'
	 *		require: [
	 *			'myNamespace.myParentClass'
	 *		]
	 *	},{
	 *		// ...
	 *	});
	 * 
	 * @method define
	 * @param {String|Object} name The name of the class or a meta object
	 * @param {Object} body The body of the class
	 * @param {Function} callback Callback function that fires when the 
	 * 	class definition is done
	 * @async
	 **/
	define: function(name,body,callback) {
		
		// Parse meta data
		var meta=this._parseMeta(name);
		
		// Requires that the class definition has to wait for
		var waitForRequires=[];
		
		// Requires that have to be loaded now
		var triggerRequires=[];
		
		for (var i=0;i<meta.require.length;i++) {
			
			// The class is currently loading by another require
			if (this._classLoading(meta.require[i])) {
				
				// Remember that we have to wait for it
				waitForRequires.push(meta.require[i]);
				
			// The class is untouched and need to be loaded
			} else if (!this._classLoaded(meta.require[i])) {
				
				// Remember that we have to wait for it
				waitForRequires.push(meta.require[i]);
				
				// Remember that we have to trigger the load of it
				triggerRequires.push(meta.require[i]);
				
			}
			
		}
		
		// If there are requires that we have to wait for
		if (waitForRequires.length>0) {
			
			// Delay our class definition till all requires are resolved
			this._delayDefine(name,body,callback,waitForRequires);
			
			for (var i=0;i<triggerRequires.length;i++) {
				
				this._requireClass(triggerRequires[i]);
				
			}
			
		// If there is nothing we have to wait for just create the class
		} else {
			
			this._defineClass(meta,body,callback);
			
		}
		
	},
	
	/**
	 * Method for inheritance like parent class calls
	 * 
	 * Use this with the apply method to inject the local scope.
	 * 
	 *	zerk.parent('myClass').myMethod.apply(
	 *		this,
	 *		arguments
	 *	);
	 * 
	 * @method parent
	 * @param {String} name The name of the class
	 * @return {Object} The parent class
	 **/
	parent: function(name) {
		
		return this._parentClassMap[name];
		
	},
	
	/**
	 * Creates a class instance
	 * 
	 * All arguments after 'name' are passed to the init method of the class.
	 * 
	 * @method create
	 * @param {String} name The name of the class
	 * @return {Object} Class instance
	 **/
	create: function(name) {
		
		if (arguments.length==0) return;
		
		var args=Array.prototype.slice.call(arguments);
		var parent=args[0];
		
		if (typeof parent==='string') {
			
			var parentClass=this._classMap[parent];
			
		} else {
			
			var parentClass=parent;
			parent='Object';
			
		}
		
		/*
		 * TODO Create a useful error handling here
		 */
		if (typeof parentClass==='undefined') {
			
			console.log('Error: Class to create is undefined '+parent);
			return null;
			
		}
		
		var constructorArguments=args.slice(1);
		
		/*
		 * TODO Check if the _createObject method could be used here
		 */
		function f() {}
		if (typeof parentClass=='object') {
			
			f.prototype=parentClass;
			
		}
		var i=new f();
		
		if (typeof i.init!=='undefined') {
			
			i.init.apply(i,constructorArguments);
			
		}
		
		return i;
		
	},
	
	/**
	 * Defines a class
	 * 
	 * @method _defineClass
	 * @param {Object} meta Class meta object
	 * @param {Object} body Class body
	 * @param {Function} callback Callback function that fires after the 
	 * 	class was defined
	 * @return {Object} Class definition
	 * @private
	 * @async
	 **/
	_defineClass: function(meta,body,callback) {
		
		var baseClass=null;
		
		// Parse class name
		var classNameInfo=this._parseClassName(meta.name);
		
		if (meta.extend) {
			
			baseClass=this._classMap[meta.extend];
			
		} else {
			
			baseClass={};
			
		}
		
		var i=this._createObject(baseClass);
		
		for (var name in body) {
			
			i[name]=body[name];
			
		}
		
		i.$class=classNameInfo.path;
		
		// Add an entry to the class map
		this._classMap[meta.name]=i;
		
		// Add an entry to the parent class map
		this._parentClassMap[meta.name]=baseClass;
		
		if (meta.callback) {
			
			meta.callback.call();
			
		}
		
		this._classState[meta.name]='loaded';
		
		this._processLoadedClass(meta.name);
		
		if (typeof callback!=='undefined') {
			
			callback();
			
		}
		
		return this._classMap[meta.name];
		
	},
	
	/**
	 * Creates a delayed define entry and returns its handle
	 * 
	 * @method _delayDefine
	 * @param {Object} meta Class meta object
	 * @param {Object} body Class body
	 * @param {Function} callback Callback function
	 * @param {Array} waitFor
	 * @return {Object} The handle for the delayed define
	 * @private
	 * @async
	 **/
	_delayDefine: function(meta,body,callback,waitFor) {
		
		// Format waitFor array as object
		var waitForObj={};
		
		for (var i=0;i<waitFor.length;i++) {
			
			waitForObj[waitFor[i]]='require';
			
		}
		
		return this._delayedDefine[meta.name]={
			meta: meta,
			code: body,
			callback: callback,
			waitFor: waitForObj
		};
		
	},
	
	/**
	 * Creates an object from parent object definition
	 * 
	 * This is an implementation of Douglas Crockfords prototypal 
	 * inheritance in JavaScript.
	 * 
	 * <a href="http://javascript.crockford.com/prototypal.html">
	 * http://javascript.crockford.com/prototypal.html</a>
	 * 
	 * @method _createObject
	 * @param {Object} parent
	 * @return {Object} The created object
	 * @private
	 **/
	_createObject: function(parent) {
		
		/*
		 * TODO Create a useful error handling here
		 */
		if (typeof parent==='undefined') {
			
			console.log('Error: Object to create is undefined');
			return null;
			
		}
		
		function f() {}
		
		if (typeof parent=='object') {
			
			f.prototype=parent;
			
		}
		
		return new f();
		
	},
	
	/**
	 * Checks if a class is loaded and defined already
	 * 
	 * @method _classLoaded
	 * @param {String} className The name of the class
	 * @return {Boolean} Returns true if the class is loaded and defined 
	 * 	already
	 * @private
	 **/
	_classLoaded: function(className) {
		
		if (typeof this._classState[className]!=='undefined') {
			
			return (this._classState[className]=='loaded');
			
		}
		
	},
	
	/**
	 * Checks if a class is loading already
	 * 
	 * @method _classLoading
	 * @param {String} className The name of the class
	 * @return {Boolean} Returns true if the class is currently loading
	 * @private
	 **/
	_classLoading: function(className) {
		
		if (typeof this._classState[className]!=='undefined') {
			
			return (this._classState[className]=='loading');
			
		}
		
	},
	
	/**
	 * Returns the url for given class name
	 * 
	 * @method _getClassURL
	 * @param {String} className The name of the class
	 * @return {String} Class name in object key notation
	 * @private
	 **/
	_getClassURL: function(className) {
		
		var segments=className.split('.');
		
		if (segments.length<2) return;
		
		var ns=segments[0];
		
		segments.splice(0,1);
		
		var path=segments.join('/')+'.js';
		
		return ((ns=='zerk') 
			? this._config.bootstrap.zerkDir 
			: this._config.bootstrap.gameDir)
			+'/class/'+path;
		
	},
	
	/**
	 * Loads the related script file of the given class name
	 * 
	 * @method _requireClass
	 * @param {String} className
	 * @private
	 **/
	_requireClass: function(className) {
		
		// Get the target url of the class
		var url=this._getClassURL(className);
		
		// Trigger the script load
		this._loadScript(url,className);
		
		// Remember the class as loading already
		this._classState[className]='loading';
		
	},
	
	/**
	 * Loads a JavaScript file
	 * 
	 * @method _loadScript
	 * @param {String} url The target url of the script to be loaded
	 * @private
	 **/
	_loadScript: function(url) {
		
		var head=document.getElementsByTagName('head')[0];
		var script=document.createElement('script');
		
		script.type='text/javascript';
		script.src=url+'?r='+Math.random();
		script.async=true;
		
		head.appendChild(script);
		
	},
	
	/**
	 * Process delayed defines after dependencies are resolved
	 * 
	 * @method _processLoadedClass
	 * @param {String} className The name of the class
	 * @private
	 **/
	_processLoadedClass: function(className) {
		
		var entry=null;
		
		for (var name in this._delayedDefine) {
			
			entry=this._delayedDefine[name];
			
			if (typeof entry.waitFor[className]!=='undefined') {
				
				delete entry.waitFor[className];
				
				if (Object.keys(entry.waitFor).length==0) {
					
					this._defineClass(entry.meta,entry.code,entry.callback);
					delete this._delayedDefine[name];
					
				}
				
			}
			
		}
		
		// If this was the last delayed define
		if (Object.keys(this._delayedDefine).length==0) {
			
			this._onLoad();
			
		}
		
	},
	
	/**
	 * Normalize meta data
	 * 
	 * @method _parseMeta
	 * @param {String|Object} meta A class meta data object or name
	 * @return {Object} The parsed meta data object
	 * @private
	 **/
	_parseMeta: function(meta) {
		
		var result={
			name: meta.name,
			extend: ((typeof meta.extend!=='undefined') ? meta.extend : ''),
			require: ((typeof meta.require!=='undefined') ? meta.require : [])
		};
		
		if (result.extend) {
			
			var extendRequireExisting=false;
			
			for (var i=0;i<result.require.length;i++) {
				
				if (result.require[i]==result.extend) {
					
					extendRequireExisting=true;
					break;
					
				}
				
			}
			
			if (!extendRequireExisting) {
				
				result.require.push(result.extend);
				
			}
			
		}
		
		return result;
		
	},
	
	/**
	 * Parses given class name and returns detailed information
	 * 
	 * @method _parseClassName
	 * @param {String} className The name of the class
	 * @return {Object} An object containing namespace information
	 * @private
	 **/
	_parseClassName: function(className) {
		
		var path=className.split('.');
		var parent=path.slice(0,-1);
		var name=path.slice(path.length-1,path.length);
		
		return {
			name: name[0],
			parent: parent.join('.'),
			path: className
		};
		
	},
	
	/*--------------------------------------------------------------------------
	 | Error Handling And Logging
	 *------------------------------------------------------------------------*/
	
	/**
	 * Initializes the error handler
	 * 
	 * @method _initErrorHandler
	 * @private
	 **/
	_initErrorHandler: function() {
		
		/*
		 * TODO Evaluate that the error handler can be used without suppressing 
		 * 	native errors in some browsers
		 */
		return;
		
		var self=this;
		
		// Set error handler
		window.onerror=function(message,file,line) {
			
			if (self._config.bootstrap.log.enabled) {
				
				// Shutdown engine threads
				/*
				 * TODO Implement code to interrupt engine
				 */
				console.log(
					'%c*** Engine Interrupt ***',
					'background-color: yellow; color: red'
				);
				
				// Check for exit exception
				if (message.substr(0,20)=='Zerk Exit Exception:') {
				
					console.log('- EXIT -');
					
					// Suppress browser handling
					return true;
					
				// Check for error exception
				} else if (message.substr(0,15)=='Zerk Exception:'
				&& self._config.bootstrap.log.wrapErrors) { 
					
					// Suppress browser handling
					return true;
					
				} else {
					
					/*
					 * TODO Validate that the error handler never suppresses native errors
					 */
					console.log('E',message);
					
					return false;
					
				}
				
			} else {
				
				console.log('E',message);
					
				return false;
				
				/*
				 * TODO Implement code to handle errors in production
				 */
				// Suppress browser handling
				//return true;
				
			}
			
		};
		
	},
	
	/**
	 * Log message
	 * 
	 * The message can be specified as object with additional options:
	 * 
	 * 	zerk.log({
	 * 		message: 'Log message',
	 * 		group: 'Custom Group', 
	 * 		severity: 2
	 * 	})
	 * 
	 * @method log
	 * @param {String|Object} message Log message
	 **/
	log: function(message) {
		
		var config=this._config.bootstrap.log;
		
		var entry=message || {};
		
		if (typeof entry=='string') {
			
			entry={message: entry};
			
		}
		
		if (typeof entry.severity=='undefined') {
			
			entry.severity=1;
			
		}
		
		if (typeof entry.group=='undefined') {
			
			entry.group='';
			
		}
		
		var severityCondition=(
			config.severity==0 
			|| entry.severity<=config.severity
		);
		
		var filterCondition=true;
		if (config.groupFilter!=null) {
			
			filterCondition=zerk.inArray(
				entry.group,
				config.groupFilter
			);
			
		}
		
		if (!config.enabled
		|| !severityCondition
		|| !filterCondition) {
			
			return;
			
		}
		
		var severenityIndicator='',
			label='';
		
		severenityIndicator='';
		
		while (severenityIndicator.length<(entry.severity-1)*2) {
			
			severenityIndicator+='. ';
			
		}
		
		label=severenityIndicator
			//+((entry.severity>1) ? ' ' : '')
			+((entry.group) ? entry.group+':' : '');
		
		if (label) {
			
			console.info(label,entry.message);
			
		} else {
			
			console.info(entry.message);
			
		}
		
	},
	
	/**
	 * Warn message
	 * 
	 * The message can be specified as object with additional options:
	 * 
	 * 	zerk.warn({
	 * 		message: 'Log message',
	 * 		group: 'Custom Group'
	 * 	})
	 * 
	 * @method warn
	 * @param {String|Object} message Warn message
	 **/
	warn: function(message) {
		
		warning=message || {};
		
		if (typeof warning=='string') {
			
			warning={message: warning};
			
		}
		
		if (typeof warning.group=='undefined') {
			
			warning.group='';
			
		}
		
		if (!this._config.bootstrap.log.enabled) return;
		
		label=((warning.group) ? warning.group+':' : '');
		
		if (label) {
			
			console.warn(label,warning.message);
			
		} else {
			
			console.warn(warning.message);
			
		}
		
	},
	
	/**
	 * Raise error
	 * 
	 * The message can be specified as object with custom data:
	 * 
	 * 	zerk.error({
	 * 		message: 'Log message',
	 * 		customData: 'Debug me'
	 * 	})
	 * 
	 * @method error
	 * @param {String|Object} message Error message
	 **/
	error: function(message) {
		
		// Force string into object
		
		error=message || {};
		
		if (typeof message=='string') {
			
			error={message: error};
			
		}
		
		// Extract class and method names from source property
		if (error.source) {
			
			var caller=arguments.callee.caller;
			
			error.sourceClass=error.source.$class;
			
			for (var member in error.source) {
				
				if (typeof error.source[member]=='function'
				&& error.source[member]==caller) {
					
					error.sourceMethod=member;
					break;
					
				}
				
			}
			
			// Delete source to prevent browser crash!?
			delete error.source;
			
		}
		
		// Extend native error object
		
		var zerkException=function(exception) {
			
			// Force string into object
			
			data=exception || {};
			
			if (typeof data=='string') {
				
				data={message: data};
				
			}
			
			// Apply properties
			zerk.apply(this,data);
			
			// Setup error name
			this.name='Zerk Exception';
			
		};
		
		zerkException.prototype=Error.prototype;
		
		zerkException.prototype.toString=function() {
			
			return this.message || '(Empty message)';
			
		};
		
		var exception=new zerkException(error);
		
		if (this._config.bootstrap.log.enabled 
		&& this._config.bootstrap.log.wrapErrors) {
			
			// Display wrapped error message
			
			// Start group
			console.group('Zerk Error');
			
			// console.error to render the message with stacktrace
			console.error(exception);
			
			// Extract meta data
			var meta={};
			zerk.apply(meta,error);
			delete meta.message;
			
			// console.dir to display meta data
			console.dir(meta);
			
			// End group
			console.groupEnd();
			
		}
		
		// Throw to halt the execution
		throw exception;
		
	},
	
	/**
	 * Halt execution
	 * 
	 * Throws an exception to halt the execution. Used for debbuging purposes.
	 * 
	 * @method exit
	 **/
	exit: function() {
		
		// Create exit exception
		
		var zerkExitException=function() {
			
			this.name='Zerk Exit Exception';
			this.message='This is not an error';
			
		};
		
		zerkExitException.prototype=Error.prototype;
		
		throw new zerkExitException();
		
	},
	
	/*--------------------------------------------------------------------------
	 | Type Check Methods
	 *------------------------------------------------------------------------*/
	
	/**
	 * Returns the type of the given variable as string.
	 * 
	 * Possible values are:
	 *
	 * 	"undefined"
	 * 	"null"
	 * 	"string"
	 * 	"number"
	 * 	"boolean"
	 * 	"date"
	 * 	"function"
	 * 	"object"
	 * 	"array"
	 * 	"regexp"
	 * 	"element"
	 *
	 * @method typeOf
	 * @param {Mixed} value
	 * @return {String}
	 */
	typeOf: function(value) {
		
		if (value===null) {
			
			return 'null';
			
		}
		
		var type=typeof value;
		
		if (type==='undefined'
		|| type==='boolean'
		|| type==='number'
		|| type==='string') {
			
			return type;
			
		}
		
		switch(Object.prototype.toString.call(value)) {
			case '[object Boolean]': return 'boolean';
			case '[object Number]': return 'number';
			case '[object Date]': return 'date';
			case '[object RegExp]': return 'regexp';
			case '[object Array]': return 'array';
		}
		
		if (type==='function') {
			
			return 'function';
			
		}
		
		if (type==='object') {
			
			if (value.nodeType!==undefined) {
				return 'element';
			}
			
			return 'object';
			
		}
		
	},
	
	/**
	 * Returns true if the given variable is not undefined.
	 * 
	 * @method isDefined
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isDefined: function(value) {
		
		return typeof value!=='undefined';
		
	},
	
	/**
	 * Returns true if the given variable is boolean.
	 *
	 * @method isBoolean
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isBoolean: function(value) {
		
		return typeof value==='boolean';
		
	},
	
	/**
	 * Returns true if the given variable is a number.
	 * 
	 * @method isNumber
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isNumber: function(value) {
		
		return typeof value==='number' && isFinite(value);
		
	},
	
	/**
	 * Returns true if the given variable is a string.
	 * 
	 * @method 
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isString: function(value) {
		
		return typeof value==='string';
		
	},
	
	/**
	 * Returns true if the given variable is a date.
	 * 
	 * @method isDate
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isDate: function(value) {
		
		return Object.prototype.toString.call(value)==='[object Date]';
		
	},
	
	/**
	 * Returns true if the given variable is an array.
	 *
	 * Link to the native "Array.isArray" method is possible.
	 *
	 * @method isArray
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isArray: ('isArray' in Array) ? Array.isArray : function(value) {
		
		return Object.prototype.toString.call(value)==='[object Array]';
		
		/*
		// Alternate code
		if (o!=null && typeof o=='object') {
			return (typeof o.push=='undefined') ? false : true;
		} else {
			return false;
		}
		*/
		
	},
	
	/**
	 * Returns true if the given variable is an object.
	 * 
	 * @method isObject
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isObject: function(value) {
		
		return Object.prototype.toString.call(value)==='[object Object]';
		
	},
	
	/**
	 * Returns true if the given variable is a function.
	 * 
	 * @method isFunction
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isFunction: function(value) {
		
		return typeof value==='function';
		
	},
	
	/**
	 * Returns true if the given variable a primitive type.
	 * 
	 * Primitive types are boolean, number and string.
	 * 
	 * @method isPrimitiveType
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isPrimitiveType: function(value) {
		
		var type=typeof value;
		
		return type==='string' || type==='number' || type==='boolean';
		
	},
	
	/*--------------------------------------------------------------------------
	 | Value Check methods
	 *------------------------------------------------------------------------*/
	
	/**
	 * Returns true if the given variable is empty.
	 *
	 * Empty means:
	 * 
	 * Null  
	 * Undefined  
	 * Empty array  
	 * Empty string  
	 * 
	 * @method isEmpty
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isEmpty: function(value) {
		
		return (value===null)
			|| (value===undefined)
			|| (value==='')
			|| (this.isArray(value) && value.length===0);
		
	},
	
	/**
	 * Returns true if the given variable contains a numeric value.
	 * 
	 * @method isNumeric
	 * @param {Object} value
	 * @return {Boolean}
	 */
	isNumeric: function(value) {
		
		return !isNaN(parseFloat(value)) && isFinite(value);
		
	},
	
	/*--------------------------------------------------------------------------
	 | Language Helpers
	 *------------------------------------------------------------------------*/
	
	/**
	 * Apply properties to an object/class
	 * 
	 * @method apply
	 * @param {Object} obj The object witch the properties should be applied to
	 * @param {Object} props A JSON property structure
	 */
	/*
	 * TODO Rename the method apply to dont collide with native method
	 */
	/*
	 * TODO Refactor the apply method
	 */
	apply: function(obj,props) {
		
		for (var name in props) {
			
			if (this.isObject(obj[name])
			&& this.isObject(props[name])) {
				
				this.apply(obj[name],props[name]);
				
			} else {
				
				obj[name]=this.clone(props[name]);
				
			}
			
		}
		
	},
	
	/**
	 * Clone variable
	 * 
	 * Returns a deep clone of given variable.
	 * 
	 * @method clone
	 * @param {Any} value The variable to clone
	 **/
	clone: function(value) {
		
		/*
		 * TODO Validate that this clone method works and refactor
		 */
		
		var item=value;
		
		var self=this;
		
		if (!item) { return item; } // null, undefined values check
		
		var types = [ Number, String, Boolean ], 
			result;
		
		// normalizing primitives if someone did new String('aaa'), or new Number('444');
		types.forEach(function(type) {
			if (item instanceof type) {
				result = type( item );
			}
		});
		
		if (typeof result == "undefined") {
			if (Object.prototype.toString.call( item ) === "[object Array]") {
				result = [];
				item.forEach(function(child, index, array) { 
					result[index] = self.clone( child );
				});
			} else if (typeof item == "object") {
				// testing that this is DOM
				if (item.nodeType && typeof item.cloneNode == "function") {
					var result = item.cloneNode( true );	
				} else if (!item.prototype) { // check that this is a literal
					if (item instanceof Date) {
						result = new Date(item);
					} else {
						// it is an object literal
						result = {};
						for (var i in item) {
							result[i] = self.clone( item[i] );
						}
					}
				} else {
					// depending what you would like here,
					// just keep the reference, or create new object
					if (false && item.constructor) {
						// would not advice to do that, reason? Read below
						result = new item.constructor();
					} else {
						result = item;
					}
				}
			} else {
				result = item;
			}
		}
		
		return result;
		
	},
	
	/**
	 * Removes duplicate values from an array
	 * 
	 * Takes an input array and returns a new array without duplicate values. 
	 * 
	 * @method arrayUnique
	 * @param {Array} data The input array
	 * @return {Array} Returns the filtered array
	 **/
	arrayUnique: function(data) {
		
		var result=[];
		var existing={};
		
		for (var i=0;i<data.length;i++) {
			
			/*
			 * TODO Check if all 'undefined' should be replaced with undefined without quotes
			 */
			if (typeof existing[data[i]]=='undefined') {
				
				result.push(data[i]);
				
				existing[data[i]]=true;
				
			}
			
		}
		
		return result;
		
	},
	
	/**
	 * Return all the values of an object
	 * 
	 * Returns all the values from the input object in an array.
	 * 
	 * @method objectValues
	 * @param {Object} data The input object
	 * @return {Array} Returns the values of the object as array
	 **/
	objectValues: function(data) {
		
		var result=[];
		
		for (var key in data) {
			
			result.push(data[key]);
			
		}
		
		return result;
		
	},
	
	/**
	 * Returns the names of the object members
	 * 
	 * Returns the names of the object members in an array.
	 * 
	 * @method objectKeys
	 * @param {Object} data The input object
	 * @return {Array} Returns the names of the object members as array
	 **/
	objectKeys: function(data) {
		
		var result=[];
		
		for (var key in data) {
			
			result.push(key);
			
		}
		
		return result;
		
	},
	
	/**
	 * Returns the count of the members of given object
	 * 
	 * @method objectCount
	 * @param {Object} data The input object
	 * @return {Integer} Member count
	 **/
	objectCount: function(data) {
		
		var result=0;
		
		for (var member in data) {
			
			result++;
			
		}
		
		return result;
		
	},
	
	/**
	 * Checks if a value exists in an array
	 * 
	 * Searches haystack for needle using. 
	 * 
	 * @method inArray
	 * @param {String} needle The searched value
	 * @param {Array} haystack The array
	 * @return {Boolean} True when needle in contained in haystack
	 **/
	inArray: function(needle,haystack) {
		
		for (var i=0;i<haystack.length;i++) {
			
			if (haystack[i]==needle) {
				
				return true;
				
			}
			
		}
		
		return false;
		
	}
	
};