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 30th in o2.js Modules by .

Designing a JavaScript String Helper

Air on a G String

Air on a G String

String is a data type that any developer spends most of time working with. We format, we replace, we sanitize, we prettify, we trim, we encode, we decode, we serializeStrings tirelessly, over and over :) And it’s virtually impossible to think about a JavaScript framework without some kind of a “String Helper” construct.

In this article, we’re going to create a modular o2.StringHelper object.

You can click here, to get the source code of the entire StringHelper suite (8Kb zipped archive) »»

o2.StringHelper Core

First, let’s start with creating a very basic module with only a few core methods

Those methods will be the ones that we are sure we’ll be using almost always — hence the name “Core”.

//file: o2.stringhelper.core.js

if(typeof o2 == 'undefined') {
	o2 = {};
}

/**
 * Package: o2.stringhelper.core
 * Module: o2.StringHelper
 * Dependencies: none (can run standalone)
 *
 * A String helper.
 */
( function(o2, window, undefined) {
	/** 
	 * Module Configuration
	 */
	var config = {
		constants: {
			GUID_MULTIPLIER: 10000,
			regExp: {
				TRIM: /^\s+|\s+$/g,
				WHITESPACE:/\s+/g
			}
		}
	};

	o2.StringHelper = {
		/**
		 * Creates a globally unique identifier (for that browsing session);
		 * @return a unique GUID.
		 */
		createGuid: function() {
			return [(new Date()).getTime(), Math.floor(config.constants.GUID_MULTIPLIER * Math.random())].join('');
		},

		/**
		 * Concatanes all its arguments into a single String.
		 * This is faster than adding those strings with +.
		 * @return the concataneted String.
		 */
		concat: function() {
			return Array.prototype.slice.call(arguments).join('');
		},

		/**
		 * Works similar to C#'s String.Format.
		 *
		 * Usage Example:
		 * 		o2.StrinHelper.format("Hello {0}. What's going on in {1}?", 'Ninja',
		 * 'California');
		 * 		//will return "Hello Ninja. What's going on in California"
		 * @return the formated String.
		 */
		format: function() {
			var args = arguments;
			var pattern = RegExp(['{', '([1-' , (args.length-1) , '])' , '}'].join(''), 'g');

			return string.replace(pattern, function(match, index) {
				return args[index];
			});

		},

		/**
		 * Simply removes the phrases that match the regExp from the String.
		 * @param {String} str - the String to process.
		 * @param {RegExp} regExp - the regular expression to process agains.
		 * @return the processed String.
		 */
		remove: function(str, regExp) {
			return str.replace(regExp, '');
		},

		/**
		 * Trims white space from beginning and end of the String.
		 * @param {String} str - the String to process.
		 * @param {Boolean} shouldCompact - Optional (default: false)
		 * 		if true, multiple whitespace is compacted into single whitespace.
		 * @return the processed String.
		 */
		trim: function(str, shouldCompact) {
			shouldCompact = shouldCompact || false;
			var constants = config.constants;
			var regExp = constants.regExp

			return shouldCompact ? str.replace(regExp.WHITESPACE, ' '
			).replace(regExp.TRIM, ''):str.replace(regExp.TRIM, '');
		}

	};
}(o2, this));

Note that we’ve encapsulated variables that may be prone to change in a global config variable. This is a good practice to follow, if you are developing re-usable libraries.

Secondly, even though the functions are self-explanatory, we’ve documented our code. We’ll be dealing with a method to automagically create an HTML API documentation using these JS-Doc comments, in a following tutorial. Then it will be more obvious how crucial it is to write a solid documentation for your library.

o2.StringHelper “Transform” Partial Module

Now that we have the core object, let us extend it with additional methods, by module augmentation:

//file: o2.stringhelper.transform.js

if(typeof o2 == 'undefined') {
	var o2 = {};
}

if(o2.StringHelper == undefined) {
	o2.StringHelper = {};
}

/**
 * Package: o2.stringhelper.transform
 * Module: o2.StringHelper
 * Dependencies: none (can run standalone)
 *
 * This package is responsible for simple
 * string stripping operations.
 */
( function(me, window, undefined) {

	var config = {
		constants: {
			TRUNCATION_LENGTH: 100,
			regExp: {
				BR_2_NL:/<br\s*\/?>/g,
				NL_2_BR:/\r\n|\n|\r/g,
				REMOVE_TAGS:/<[\/]?([a-zA-Z0-9]+)[^>^<]*>/ig,
				CAMEL_CASE: /(\-[a-z])/g,
				ALL_CAPS: /([A-Z])/g
			},
			text: {
				ELLIPSIS: '...',
				DASH: '-',
				UNDERSCORE: '_',
				NEW_LINE: '\n',
				BR: '<br />'
			}
		}
	};

	/**
	 * Replaces HTML <br /> tags with new line.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.br2nl = function(str) {
		var constants = config.constants;
		return str.replace(constants.regExp.BR_2_NL, constants.text.NEW_LINE);
	};

	/**
	 * Replaces new lines (\n] with HTML <br /> tags.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.nl2br = function(str) {
		var constants = config.constants;
		return str.replace(constants.regExp.NL_2_BR, constants.text.BR);
	};

	/**
	 * Removes all the HTML tags in the String.
	 * @param {String} str - the String to process.
	 * @return the cleaned output.
	 */
	me.removeTags = function(str) {
		return str.replace(config.constants.regExp.REMOVE_TAGS, '');
	};

	/**
	 * Add an ellipsis (...), if the length of the String is greated than
	 * maxLength.
	 * @param {String} str - the String to process.
	 * @param {Integer} maxLength - Optional (defaults to
	 * config.constants.TRUNCATION_LENGTH),
	 * 		maksimum String length that's allowed without truncation.
	 * @return the processed String.
	 */
	me.addEllipsis = function(str, maxLength) {
		var ellipsis = config.constants.text.ELLIPSIS;
		var eLen = ellipsis.length;

		maxLength = maxLength ? maxLength : config.constants.TRUNCATION_LENGTH;

		if(str.length > maxLength) {
			return [str.substr(0, maxLength-eLen), ellipsis].join('');
		}

		return str;
	};

	/**
	 * Converts the input to camel case.
	 * i.e. if input is 'lorem-ipsum', the output is 'loremIpsum'.
	 * This is especially useful for converting CSS classes
	 * to their DOM style representations.
	 * @param {String} input - the String to convert.
	 * @return the formatted String.
	 */
	me.toCamelCase = function(input) {
		var constants = config.constants;

		return input.replace(constants.regExp.CAMEL_CASE, function(match) {
			return match.toUpperCase().replace(constants.text.DASH, '');
		});

	};

	/**
	 * Converts a string of the form 'loremIpsum' to 'lorem-ipsum'.
	 * @param {String} input - the String to convert.
	 * @return the formatted String.
	 */
	me.toDashedFromCamelCase = function(input) {
		var constants = config.constants;

		return input.replace(constants.regExp.ALL_CAPS, function(match) {
			return [constants.text.DASH, match.toLowerCase()].join('');
		});

	};

	/**
	 * Converts a string of the form 'lorem-ipsum' to 'lorem_ipsum'.
	 * @param {String} input - the String to convert.
	 * @return the formatted String.
	 */
	me.toUnderScoreFromCamelCase = function(input) {
		var constants = config.constants;
		
		return input.replace(constants.regExp.ALL_CAPS, function(match) {
			return [constants.text.UNDERSCORE, match.toLowerCase()].join('');
		});

	};

}(o2.StringHelper, this));

As you see, we try to create our module as much independent from other modules as possible.

For instance, instead of using o2.StringHelper.remove from o2.stringhelper.core.js file, we prefer to use str.replace(‘string’, regExp) instead, so that we don’t create a dependency to the StringHelper core file.

o2.StringHelper “Strip” Partial Module

If we design our modules without inter-dependency, then we can lazy-load and mix them in any order we like, which is a great advantage ;)

Let’s define another independent partial module:

//file: o2.stringhelper.strip.js

if(typeof o2 == 'undefined') {
	var o2 = {};
}

if(o2.StringHelper == undefined) {
	o2.StringHelper = {};
}

/**
 * Package: o2.stringhelper.strip
 * Module: o2.StringHelper
 * Dependencies: none (can run standalone)
 *
 * This package is responsible for simple string stripping operations.
 */
( function(me, window, undefined) {

	/**
	 * Removes non alphanumeric characters from the String.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.stripNonAlpha = function(str) {
		return str.replace(/[^A-Za-z ]+/g,'');
	};

	/**
	 * Removes alpha numeric characters from the String.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.stripNonAlphaNumeric = function(str) {
		return str.replace(/[^A-Za-z0-9 ]+/g, '');
	};

	/**
	 * Removes non numeric characters from the String.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.stripNonNumeric = function(str) {
		return str.replace(/[^0-9-.]/g, '');
	};

	/**
	 * Removes numeric characters from the String.
	 * @param {String} str - the String to format.
	 * @return the formatted String.
	 */
	me.stripNumeric = function(str) {
		return str.replace(/[0-9]/g, '');
	};

}(o2.StringHelper, this));

o2.StringHelper “Generate” Partial Module

This is just a draft. We may be adding more methods to this module.

//file:o2.stringhelper.generate.js

if(typeof o2 == 'undefined') {
	var o2 = {};
}

if(o2.StringHelper == undefined) {
	o2.StringHelper = {};
}

/**
 * Package: o2.stringhelper.generate
 * Module: o2.StringHelper
 * Dependencies: none (can run standalone)
 *
 * This package is responsible for simple string generations.
 */
( function(me, window, undefined) {
	
	/** 
	 * Module configuration.
	 */
	var config = {
		defaultRandomLength : 8
	};
	
	/** 
	 * Generates a random string
	 * @param {Integer} length - (optional - default: config.defaultRandomLength)
	 * 		length of the String to be generated.
	 * @return the generated String.
	 */
	me.generateRandom = function(length) {
		var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';

		var len = length || config.defaultRandomLength;
		var charsLength = chars.length;
		var randomString = '';
		var randomNumber = 0;
		
		var buffer = [];
		
		for (var i = 0; i < len; i++) {
			randomNumber = Math.floor(Math.random() * charsLength);
			buffer.push(chars.substring(randomNumber, randomNumber + 1))
		}
		
		return buffer.join('');
	};

}(o2.StringHelper, this));

o2.StringHelper “Encode” Partial Module

May be this module will be doing more convoluted encoding/decoding operations in the future.
Currently it’s in its draft form, waiting to be evolved as needed.

//file: o2.stringhelper.encode.js

if(typeof o2 == 'undefined') {
	var o2 = {};
}

if(o2.StringHelper == undefined) {
	o2.StringHelper = {};
}

/**
 * Package: o2.stringhelper.core
 * Module: o2.StringHelper
 * Dependencies: none (can run standalone)
 *
 * Responsible for encoding and decoding Strings.
 */
( function(me, window, undefined) {
	/**
	 * Encodes special charaters to their corresponding HTML entities.
	 * If possible try using this try using standard
	 * encoding methods like encodeURIComponent, instead of
	 * using this method.
	 * @param {String} str - the String to process
	 * @return the processed String.
	 */
	me.encode = function(str) {
		return str
		.replace(/&/g, '&amp;')
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
		.replace(/"/g, '&quot;')
		.replace(/ /g, '&nbsp;');
	};

	/**
	 * Decodes HTML entities back to normal characters.
	 * If possible try using this try using standard
	 * decoding methods like decodeURIComponent, instead of
	 * using this method.
	 * @param {String} str - the String to process
	 * @return the processed String.
	 */
	me.decode = function() {
		return str
		.replace(/&#60;|&lt;/g, '<')
		.replace(/&#62;|&gt;/g, '>')
		.replace(/&#34;|&quot;|&quott;/g, '"')
		.replace(/&#39;|&apos;|&aposs;/g, '\'')
		.replace(/&#32;|&nbsp;/g, ' ')
		.replace(/&#38;|&amp;/g, '&');
	};

}(o2.StringHelper, this));

Conclusion

We’ve seen a demonstration of how to create a utility object by splitting it into smaller partial modules.

We’ve also seen that it’s important to factor out important configuration data, to give flexibility to our modules.

We could, very well, have developed a giant StringHelper static class and lump all the functionality to it, in one big file. However, chunking our module to logical sub-modules and separating each module in its own file makes them more manageable and flexible ;)

Do you have any questions?
Can we improve this methodolody further?
I’d love to hear your comments :)

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

4 Comments

Leave a Reply





o2.js _
Fork Ribbon