My Javascript toolbox

Past entries

Lately I've been thinking about sharing a little more on my blog, so I decided to make quick tutorials, or to share some code, instead of just announcing my new games.

So let's start with this Javascript toolbox, which I use in all my games now. It features simple DOM manipulation, simplified AJAX and cookie usage, and many other useful functions.

The goal, of course, is not to replace heavier and more popular libraries such as jQuery or Modernizr, but to give a lightweight alternative to them, when all you need is to simplify your code. What's more, it is composed of functions that I use every day to create games, so it is well suited for common game logic.

The DOM part was highly inspired by Code inComplete's Javascript racer.

I hope you will find it as useful as it is to me ;-)

/**
 * Javascript toolbox
 * By Rémi Vansteelandt - http://www.remvst.com/
 * 
 * This toolbox should be useful for common operations when creating 
 * HTML5 games.
 * It features simple DOM manipulation, simplified AJAX and cookie usage,
 * as well as many useful functions.
 * 
 */
 
// animationFrame API
var requestAnimFrame = window.requestAnimFrame = (function(){
	return  window.requestAnimationFrame       || 
			window.webkitRequestAnimationFrame || 
			window.mozRequestAnimationFrame    || 
			window.oRequestAnimationFrame      || 
			window.msRequestAnimationFrame     || 
			function(callback){
				window.setTimeout(callback,1000/60);
			};
})();
var cancelAnimFrame = window.cancelAnimFrame = (function(){
	return  window.cancelAnimationFrame       || 
			window.webkitCancelAnimationFrame || 
			window.mozCancelAnimationFrame    || 
			window.oCancelAnimationFrame      || 
			window.msCancelAnimationFrame     || 
			function(){
				window.clearTimeout.apply(window,arguments);
			}
})();

// Creating a fake console if needed
// It could be better to use a DOM-based console in some cases.
var console = (function(){
	return window.console
			|| { 
				log : new Function(),
				debug : new Function(),
				warn : new Function(),
				error : new Function(),
				clear : new Function()
			 };
})();

// DOM class to simplify simple DOM operations
var DOM = {
	/**
	 * Returns the element matching the specified id.
	 */
	get : function(el){
		return (el == document || el == window || el instanceof HTMLElement ? el : document.getElementById(el));
	},
	/**
	 * Returns the specified attribute of the specified element.
	 */
	attr : function(el,attr,value){
		if(value){
			this.get(el).setAttribute(attr,value);
		}else{
			return this.get(el).getAttribute(attr);
		}
	},
	/**
	 * Adds an event listener to the specified element.
	 */
	on : function(el,evt,handler){
		var split = evt.split(' ');
		for(var i in split){
			this.get(el).addEventListener(split[i],handler,false);
		}
	},
	/**
	 * Removes the specified event handler.
	 */
	un : function(el,evt,handler){
		var split = evt.split(' ');
		for(var i in split){
			this.get(el).removeEventListener(split[i],handler,false);
		}
	},
	/**
	 * Shows the specified element.
	 */
	show : function(el){
		this.get(el).style.display = 'block';
	},
	/**
	 * Hides the specified element.
	 */
	hide : function(el){
		this.get(el).style.display = 'none';
	},
	/**
	 * Gets the element's position on the document.
	 */
	offset : function(el) {
		el = this.get(el);
		
		var pos = {x:0,y:0};
		do {
			pos.x += el.offsetLeft || 0;
			pos.y += el.offsetTop || 0;
		} while ((el = el.parentNode) !== null);		
		return pos;
	},
	/**
	 * Returns an array of elements matching the query.
	 */
	query : function(query){
		if(!document.querySelectorAll)
			return null;
			
		var q = document.querySelectorAll(query);
		return q;
	},
	/**
	 * Returns the element returned by the query.
	 */
	queryOne : function(query){
		if(!document.querySelector)
			return null;
			
		var q = document.querySelector(query);
		return q;
	},
	/**
	 * Creates an element of the specified type.
	 */
	create : function(type){
		return document.createElement(type);
	},
	/**
	 * Gets the position relative to the given element for the given 
	 * coordinates.
	 */
	positionRelativeTo : function(element,clientX,clientY){
		var offset = DOM.offset(element);
		return {
			x : clientX - offset.x,
			y : clientY - offset.y
		}
	},
	/**
	 * Makes the specified element fit screen size.
	 * Once the window is resized, you will need to call this function
	 * again.
	 */
	fitScreen : function(element,ratio){
		var clientRatio = window.innerWidth / window.innerHeight;
		
		var width, height;
		if(clientRatio <= ratio){
			width = window.innerWidth;
			height = width / ratio;
		}else{
			height = window.innerHeight;
			width = height * ratio;
		}
		
		element = DOM.get(element);
		element.style.width = width + 'px';
		element.style.height = height + 'px';
	}
}

// Utility class
var Util = {
	/**
	 * Preloads a set of images.
	 */
	preload : function(images,callbackProgress,callbackEnd){
		var loadOne = function(){
			if(remaining.length == 0){
				end(loaded);
			}else{				
				// Take one image from the array of remaining images.
				var img = new Image();
				img.onerror = function(){
					console.log('Couldnt load ' + this.src);
				}
				img.onload = function(){
					if(this.complete){
						progress(this,1 - remaining.length / nbImages);
						loadOne(); // loading the next one
					}
				}
				
				var src = remaining.pop();
				img.src = src;
				loaded[src] = img; // we need the unmodified src attribute
			}
		}
		
		var remaining = images.slice(0);
		var end = callbackEnd || new Function();
		var progress = callbackProgress || new Function();
		var nbImages = remaining.length;
		var loaded = {}; // hashmap src => image
		
		setTimeout(loadOne,1); // Loading the first one asynchronously
	},
	/**
	 * Generates a random floatting point number between min and max.
	 */
	rand : function(min,max){
		return Math.random() * (max - min) + min;
	},
	/**
	 * Randomly picking a value from the parameters.
	 */
	randomPick : function(){
		var i = parseInt(Util.rand(0,arguments.length));
		return arguments[i];
	},
	/**
	 * Returns n if between min and max, min if lower than min, max if higher than max.
	 */
	limit : function(n,min,max){
		if(n < min)
			return min;
		else if(n > max)
			return max;
		else
			return n;
	},
	/**
	 * Getting the sign of a number.
	 */
	sign : function(n){
		if(n > 0) 		return 1;
		else if(n == 0)	return 0;
		else 			return -1;
	},
	/**
	 * Setting and getting cookies
	 */
	cookie : {
		/**
		 * Setting a cookie with the specified key with the specified TTL
		 */
		set : function(name,value,ttl){
			// By default, a cookie is available for one year
			if(ttl == undefined)
				ttl = 1000 * 3600 * 24 * 365;
			
			document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT";
			
			var expires = new Date();
			expires.setTime(expires.getTime() + ttl);
			
			document.cookie = [
				name+'='+value+'; ',
				'expires='+expires.toGMTString() +'; ',
				'path=/'
			].join('');
		},
		/**
		 * Getting the value of a cookie with the specified key.
		 */
		get : function(name){
			var cookie = document.cookie.split('; ');
			for(var i in cookie){
				var spl = cookie[i].split('=');
				if(spl.length == 2 && spl[0] == name){
					return spl[1];
				}
			}
			return undefined;
		}
	},
	/**
	 * Deep copy of an object into another.
	 */
	copyTemplate : function(template,object){
		for(var i in template){
			if(!(i in object)){
				object[i] = template[i]
			}else{
				if(typeof(template[i]) == 'object' && !(object[i] instanceof Array)){
					arguments.callee.call(this,template[i],object[i]);
				}
			}
		}
	},
	/**
	 * Returns true if the client is using a touchscreen.
	 */
	isTouchScreen : function(){
		return (
			'ontouchstart' in document.documentElement 
			|| window.location.search.indexOf('touch') >= 0 // Allows you to test your touchscreen-specific code on a regular PC
		);
	}
};

// AJAX-related functions
var Ajax = {
	/**
	 * Sends an AJAX request.
	 */
	send : function(url,method,params,success,fail){
		// Creating the right object.
		var xhr;
		if(window.XMLHttpRequest){ // Firefox et autres
			xhr = new XMLHttpRequest();
		}else if(window.ActiveXObject) {// Internet Explorer
			try{
				xhr = new ActiveXObject("Msxml2.XMLHTTP");
			}catch (e){
				xhr = new ActiveXObject("Microsoft.XMLHTTP");
			}
		}else { // Not supported by the browser
			console.log('AJAX not supported by your browser.');
			return false;
		}
		
		// Creating default parameters if needed.
		success = success || new Function();
		fail = fail || new Function();
		method = method.toUpperCase();
		params = params || {};
		
		// Creating parameters string
		var paramsArray = [];
		for(var i in params){
			paramsArray.push(i + '=' + params[i]);
		}
		var paramsString = paramsArray.join('&');
		
		if(method == 'GET'){
			url += '?' + paramsString;
		}
		
		xhr.open(method,url, true);
		xhr.onreadystatechange = function(){
			if(xhr.readyState != 4)	
				return;
				
			if(xhr.status != 200){
				fail(xhr.status,xhr.responseText);
			}else{
				success(xhr.status,xhr.responseText);
			}
		};
		
		if(method == 'POST'){
			xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
			xhr.send(paramsString);
		}else{
			xhr.send(null);
		}
	}
};
< Canvas star effect
PxWars >