Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
April 26th in Factory, Patterns and Practices, Tutorial by .

Charlie’s Chocolate Factory in JavaScript

chocolate factory

Charlie's Chocolate Factory

The o2.Debugger that we’ve created a priori, has a single println method that simply outputs what’s sent to it as a parameter to the console or a selected DOM container.

In this discussion, we’ll try to polish o2.Debugger further and add three utility methods: assert, error, and log.

In the meantime, we’ll be creating a simple delegate Factory.

We will be using all these methods in the upcoming tutorials.

You can click here, to get the entire source code for this tutorial (2.91Kb zipped archive) »»

Let us start buy observing how o2.Debugger.println method currently looks like:

println:function(value){
	//if not initialized, then we cannot use any of
	//o2.Debugger's public methods.
	if(!config.isInitialized){ return; }

	//This part is a "Factory" implementation which crates
	//an optimized function given the two initial config parameters.
	if(config.isUsingConsole && config.outputElement){
		o2.Debugger.println = function(value){
			console.log(value);
			var debugContent = document.createElement('div');
			var valueNode = document.createTextNode(value);
			debugContent.appendChild(valueNode);
			config.outputElement.appendChild(debugContent);
		};
	} else if(config.isUsingConsole && !config.outputElement){
		o2.Debugger.println = function(value){ console.log(value); };
	} else if(!config.isUsingConsole && config.outputElement){
		o2.Debugger.println = function(value){
			var debugContent = document.createElement('div');
			var valueNode = document.createTextNode(value);
			debugContent.appendChild(valueNode);
			config.outputElement.appendChild(debugContent);
		};
	} else {
		o2.Debugger.println = function(value){};
	}

	//call the newly created method
	o2.Debugger.println(value);
},

Introducing the Factory Pattern

When I see such an ongoing if-else (or switch) chain, the immediate thing that comes to my mind is a Factory Pattern.

Let us modify this if/else chain according to the Factory Pattern:

println:function(value){
	if(!config.isInitialized){ return; }
	
	//Create a new printer method
	o2.Debugger.println = PrinterFactory.create(config);

	//call the newly created method
	o2.Debugger.println(value);
},

Simpler and more understandable, huh?

Factoring Out Magic Words and Magic Numbers

We’ll come to the PrinterFactory soon.
But before that let’s prettify our debug output a little:

/** 
 * Module configuration.
 */
var config = {
	...
	constants : {
		className: {
			/** @constant */
			FAIL: 'fail',
			/** @constant */
			PASS: 'pass',
			/** @constant */
			ERROR: 'error',
			/** @constant */
			LOG: 'log'
		},
		text: {
			/** @constant */
			PASS: '<b>PASS:</b> ',
			/** @constant */
			FAIL: '<b>FAIL:</b> ',
			/** @constant */
			ERROR: '<b>ERROR:</b> '
		}
	}
};

We’ve defined various class names and string prefixes that can be used on different debug states (error, success, and fail).

It’s a good practice to declare configuration consants for “magic strings” and “magic numbers“.

A magic string is any string that is used in more than one place and has high probability to change. Similarly, a magic number is any numeric constant or enumeration that is prone to change.

Let’s modify our println function so that it takes a className parameter and renders the line correspondingly:

/**
 * Prints the string representation of value to the next line.
 * @param {String} value - the value to print.
 * @param {String} className - the CSS class name that is associated with the line.
 */
println:function(value, className){
	//if not initialized, then we cannot use any of
	//o2.Debugger's public methods.
	if(!config.isInitialized){ return; }
	//reset className if not given.
	if(!className) { className = config.constants.className.LOG;}
	
	//Create a new printer method
	o2.Debugger.println = PrinterFactory.create(config);

	//call the newly created method
	o2.Debugger.println(value, className);
},

And then create a few utility methods that wrap around this println and use our constants that we’ve defined in the config section above:

/** 
 * Checks the value of pass, and displays the message with a proper classname.
 * The class name can be one of the (config.constant.className) members.
 * Usage Example:
 * 		o2.Debugger.assert((1==true), '1 == true');
 * @param {Expression} pass - the expression to evaluate.
 * @param {String} message - the message to display.
 */
assert: function(pass, message){
	if(!config.isInitialized){ return; }
	
	var className = config.constants.className;
	var text = config.constants.text;
	
	if(pass){
		o2.Debugger.println([text.PASS, message].join(''), className.PASS);
		return;
	}
	
	o2.Debugger.println([text.FAIL, message].join(''), className.FAIL);
},
/**
 * Prints an error message to the output.
 * Usage Example:
 * 		o2.Debugger.error('A serious error occured');
 * @param {String} message - the error message to display.
 */
error: function(message) {
	if(!config.isInitialized){ return; }

	var className = config.constants.className;
	var text = config.constants.text;

	o2.Debugger.println([text.ERROR, message].join(''), className.ERROR);
},
/** 
 * Simply logs a message.
 * Usage Example:
 * 		o2.Debugger.log('Hello world');
 * @param {String} message - the message to log.
 */
log: function(message){
	if(!config.isInitialized){ return; }

	o2.Debugger.println(message, config.constants.className.LOG);
}

One thing that may get your attention is the [text.ERROR, message].join(”) syntax. We use this syntax instead of simply ading two strings together, because string concatenation using an Array buffer consumes less resources than using the + operator (as in text.ERROR + message).

The jsdoc comments are self-explanatory for the rest of the code, since the functions are simple wrappers.

But…. Where’s the Factory?

That’s the easiest part. So I’ve left it to the end :)

/** 
 * A factory class that creates printer deleages,
 * by parsing the configuration object.
 */
var PrinterFactory = {
	/** 
	 * Returns a delegate, parsing the configuration object.
	 * Usage:
	 * 		var delegate = PrinterFactory.create(config);
	 * @param {Object} config - the configuration object.
	 * @return {Function} the proper delegate.
	 */
	create:function(config){
		if(config.isUsingConsole && config.outputElement){
			return function(value, className){
				console.log(value);
				var debugContent = document.createElement('div');
				debugContent.className = className;
				debugContent.innerHTML = value;
				config.outputElement.appendChild(debugContent);
			};
		} else if(config.isUsingConsole && !config.outputElement){
			return function(value, className){ console.log(value); };
		} else if(!config.isUsingConsole && config.outputElement){
			return function(value, className){
				var debugContent = document.createElement('div');
				debugContent.className = className;
				debugContent.innerHTML = value;
				debugContent.appendChild(valueNode);
				config.outputElement.appendChild(debugContent);
			};
		} else {
			return function(value){};
		}
	}
};

Using our new o2.Debugger is simple:

<head>
<style type="text/css">
	* {padding:0;margin:0}
	body {font-family:Verdana,Arial,Helvetica,sans-serif;font-size:12px;width:500px;margin:50px auto;}
	.error { background: #cc0000; color: #ffffff; border:1px #ff0000 solid;margin:2px;padding:10px;}
	.pass { background: #009900; color: #00ff00; border:1px #00ff00 solid;margin:2px;padding:10px;}
	.fail { background: #cccc00; color: #000; border:1px #ffff00 solid;margin:2px;padding:10px;}	
	.log { background: #fafafa; color: #000; border:1px #333 solid;margin:2px;padding:10px;}		
</style>
</head>
<body>
<script type="text/javascript" src="js/o2.debugger.js"></script>
<script type="text/javascript">
	window.onload = function(){
		o2.Debugger.init(document.getElementById('Output'), true);
		o2.Debugger.println('Hello debugging world!');
		o2.Debugger.log('this is a log test.');
		o2.Debugger.assert(10 > 5, 'this is a positive assertion test.');
		o2.Debugger.assert(10 <= 5, 'this is a negative assertion test.');
		o2.Debugger.error('this is an error test');
	};
</script>
<div id="Output">
</div>
</body>
</html>

And the output will be something like this:

debugger output

o2.Debugger output


At a Glance

  • We’ve factored out commonly used magic strings,
  • Created a Factory Class thats creates println printer delegates depending on the config,
  • And Added several helper methods for unit testing and assertion purposes.

We’ll be using these methods all, in the upcoming posts ;)

The Final o2.Debugger Object

o2.Debugger begins to look like a more complete module now:

//create an o2 namespace if not alraedy created
if(typeof o2 == 'undefined'){var o2={};}

/**
 * o2.Debugger
 * A static object for debugging purposes.
 * Sample Usage:
 *		// note: initalize o2.Debugger only once,
 *		// possibly on window.load or dom content ready
 *		o2.Debugger.init(someDomNode, true);
 *
 *		//then inside your code use this syntax.
 *		o2.Debugger.println('stuff to debug');
 */
(function(o2, window, undefined){
	//does nothing!
	o2.nill = o2.nill ? o2.nill : function(){};
	
	/** 
	 * Module configuration.
	 */
	var config = {
		/**
		 * {DOMNode} outputElement - A readonly property indicating
		 * the node to output the Debugger outcomes.
		 * This value will be set after o2.Debugger.init method is called.
		 */
		outputElement: null,
		/**
		 * {Boolean} isUsingConsole - A reaodonly property.
		 * If true browser's builting debug console is utilized.
		 * This value will be set after o2.Debugger.init method is called.
		 */
		isUsingConsole: true,
		/**
		 * {Boolean} isInitialized - A readonly property.
		 * If true, o2.Debugger has been successfully initialized.
		 * If false o2.Debugger is not initialized yet.
		 * This value will be set after o2.Debugger.init method is called.
		 */
		isInitialized: false,
		constants : {
			className: {
				/** @constant */
				FAIL: 'fail',
				/** @constant */
				PASS: 'pass',
				/** @constant */
				ERROR: 'error',
				/** @constant */
				LOG: 'log'
			},
			text: {
				/** @constant */
				PASS: '<b>PASS:</b> ',
				/** @constant */
				FAIL: '<b>FAIL:</b> ',
				/** @constant */
				ERROR: '<b>ERROR:</b> '
			}
		}
	};
	
	/** 
	 * A factory class that creates printer deleages,
	 * by parsing the configuration object.
	 */
	var PrinterFactory = {
		/** 
		 * Returns a delegate, parsing the configuration object.
	 	 * Usage:
	 	 * 		var delegate = PrinterFactory.create(config);
		 * @param {Object} config - the configuration object.
		 * @return {Function} the proper delegate.
		 */
		create:function(config){
			if(config.isUsingConsole && config.outputElement){
				return function(value, className){
					console.log(value);
					var debugContent = document.createElement('div');
					debugContent.className = className;
					debugContent.innerHTML = value;
					config.outputElement.appendChild(debugContent);
				};
			} else if(config.isUsingConsole && !config.outputElement){
				return function(value, className){ console.log(value); };
			} else if(!config.isUsingConsole && config.outputElement){
				return function(value, className){
					var debugContent = document.createElement('div');
					debugContent.className = className;
					debugContent.innerHTML = value;
					debugContent.appendChild(valueNode);
					config.outputElement.appendChild(debugContent);
				};
			} else {
				return function(value){};
			}
		}
	};

	o2.Debugger = {
		/**
		 * Initializes the Debugger.
		 * Either
		 * @param {DOMNode} outputElement - the element to append debug messages.
		 * @param {Boolean} shouldUseConsole - should browser's built-in console be used, if available.
		 */
		init: function(outputElement, shouldUseConsole){
			//can I use the browser's built-in console?
			//(the double negation !!shouldUseConsole will convert the var to boolean.)
			config.isUsingConsole = (typeof console != 'undefined' && !!shouldUseConsole);

			//is everything ok? -- I should either use the output element, or the console.
			//if I can use neither of them, then it's a fatal situation.
			var isConfigOk = ((outputElement && outputElement.nodeName) || config.isUsingConsole);

			if(!isConfigOk){ throw 'o2.Debugger: cannot initialize outputElement'; }

			//set the output element
			config.outputElement = outputElement;

			//successfully initialized.
			config.isInitialized = true;

			//prevents initializing the object more than once.
			o2.Debugger.init = o2.nill;
		},
		/**
		 * Prints the string representation of value to the next line.
		 * @param {String} value - the value to print.
		 * @param {String} className - the CSS class name that is associated with the line.
		 */
		println:function(value, className){
			//if not initialized, then we cannot use any of
			//o2.Debugger's public methods.
			if(!config.isInitialized){ return; }
			//reset className if not given.
			if(!className) { className = config.constants.className.LOG;}
			
			//Create a new printer method
			o2.Debugger.println = PrinterFactory.create(config);

			//call the newly created method
			o2.Debugger.println(value, className);
		},
		/** 
		 * Checks the value of pass, and displays the message with a proper classname.
		 * The class name can be one of the (config.constant.className) members.
		 * Usage Example:
		 * 		o2.Debugger.assert((1==true), '1 == true');
		 * @param {Expression} pass - the expression to evaluate.
		 * @param {String} message - the message to display.
		 */
		assert: function(pass, message){
			if(!config.isInitialized){ return; }
			
			var className = config.constants.className;
			var text = config.constants.text;
			
			if(pass){
				o2.Debugger.println([text.PASS, message].join(''), className.PASS);
				return;
			}
			
			o2.Debugger.println([text.FAIL, message].join(''), className.FAIL);
		},
		/**
		 * Prints an error message to the output.
		 * Usage Example:
		 * 		o2.Debugger.error('A serious error occured');
		 * @param {String} message - the error message to display.
		 */
		error: function(message) {
			if(!config.isInitialized){ return; }

			var className = config.constants.className;
			var text = config.constants.text;

  			o2.Debugger.println([text.ERROR, message].join(''), className.ERROR);
		},
		/** 
		 * Simply logs a message.
		 * Usage Example:
		 * 		o2.Debugger.log('Hello world');
		 * @param {String} message - the message to log.
		 */
		log: function(message){
			if(!config.isInitialized){ return; }

			o2.Debugger.println(message, config.constants.className.LOG);
		}
	};
}(o2, this));

Did you like the Factory Pattern, or do you prefer Umpa Lumpas doing the dirty work instead ;) ?
Your ideas, comments and suggestions are important.
I’ll be glad to hear them all :)

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

Leave a Reply





o2.js _
Fork Ribbon