String Formatting In JavaScript
A few years ago I posted a small snippet of JavaScript from a library I created way back in the bad-old days of “Classic” ASP. The post generated a handful of comments between the different blog hosts I’ve tried but only two mentioned anything about how to do it better. The rest were the standard Internet-protocol messages reminding me of how much I suck (thank you *so* much).
A couple a guys posted something more useful than just a reminder of my own total failure as a developer and a human being. Daniel Schaeffer and the code-name “R.” posted alternative solutions. Unfortunately, Mr. “R.” didn’t post a link for me to find him, but I do think I tracked him down (Google will find you).
A little history first, back when .NET 1.0 was the new hotness one of the first things I fell in love with was the String.Format() method. Simply put, given a string literal decorated with some tokens you could create template strings without having to muss about with concatenation. I know, I know, C/C++ had something like this and other languages have too. Whippty-do. I was reared on the BASIC product-line, whatever.
C#’s string template functionality was a godsend in my eyes. I had (have) an intrinsic hatred of concatenation, mostly built up from creating dynamic sites in ASP that had to work in NN4 and IE4 (hate). All the HTML, JavaScript and CSS was built on the server and spit out to the client in whole. It was a nightmare to look at because of dynamic nature of the application as almost all the output was built programmatically on the server. Let’s just say there was a lot of string concatenation. Unfortunately, moving to .NET wasn’t an option. Yet string concatenation made me so itchy, so I scratched it as is the nature of an itchy developer.
String.format("Hello, {0}.", "World");
Very simple, obviously, it didn’t contain the full sophistication of the full-blown String.Format() in .NET but it did mean I didn’t have to do this anymore.
var myContrivedExample = "<a href=\"" + protocol + "://" + url + "\">" + text+ "\"</a>";
Now I could do…
var myContrivedExample = String.format("<a href=\"{0}://{1}\">{2}\"</a>", protocol, url, text);
Yeah, just take a second and try to follow the first one and spot the mistake. Then find the mistake in the second one. Yeah, much easier, huh.
Here’s the original code snippet.
String.format = function(text) {
//check if there are two arguments in the arguments list
if (arguments.length <= 1) {
//if there are not 2 or more arguments there's nothing to replace
//just return the just3ws text
return text;
}
//decrement to move to the second argument in the array
var tokenCount = arguments.length - 2;
for (var token = 0; token <= tokenCount; ++token) {
//iterate through the tokens and replace their placeholders from the just3ws text in order
text = text.replace(new RegExp("\\{" + token + "\\}", "gi"), arguments[token + 1]);
}
return text;
};
Basically, it dynamically appends a static method to the String object. By default it expects a single argument, which can be any string. If there’s only one argument it just passes that string through unmodified. Although, if a second argument is provided the method will attempt to locate a token in the string and replace all instances of that token with the passed in string. Repeat the replacement for each following argument. Pretty straight-forward, probably a naive implementation but it got the job done.
A few years pass and I post the method on a blog.
Time churns on and the events of history progress, eventually someone reads the post.
More time churning more historical events, then someone replies to it. Neat.
Another one, wait. I know I suck.
Okay, here’s another one.
Now I’m looking back on the sad legacy of my blogging efforts till now and trying to gleam something useful from it and this is the best I can do.
Daniel Schaeffer was the first to oblige me with a response. I like what he did, it was clean and simple. Although, it wasn’t *exactly* the same.
All the comments that begin with //JUST3WS: were added by myself for the sake of the posting. And please forgive my taking some liberties with the code formatting, I’m a bit fussy.
String.prototype.format = function() {
var pattern = /\{\d+\}/g;
var args = arguments; //JUST3WS: Referencing the JavaScript 'arguments' object that is available inside all methods.
//JUST3WS: 'this' refers to the current instance of the String object.
// and the Lambda function is called for every match of the pattern.
return this.replace(pattern, function(capture) {
return args[capture.match(/\d+/)];
});
};
We can turn Mr. Schaeffer’s method into a static method relatively easily.
String.format = function() {
var pattern = /\{\d+\}/g;
var args = arguments; //JUST3WS: This stays as is.
if (args == null || args.Length == 0) {
return “”;
} //JUST3WS: The arguments might be null or empty because the
// method signature doesn't require at least one.
// JavaScript is permissive in it's parameter checks
// and will just use the closest match. So if there are
// no overloads which match better this method will still
// be invoked regardless of how many arguments are passed in.
//JUST3WS: 'this' refers to the current instance of the String object.
// and the Lambda function is called for every match of the pattern.
return this.replace(pattern, function(capture) {
//JUST3WS: This is where Lambda's can be tricky. The calling scope (caller) is visible to the executing method (callee).
// We can't use the local 'arguments' object because it would refer to the callee's arguments, not the callers. :-P
//
return args[capture.match(/\d+/)];
});
};
What’s cool about his method was actually adding an instance method to the String object. Meaning, you couldn’t just call the method in an imperative manner like my static method. But it did mean you write some really neat looking syntax. Also, he used a Lambda function to handle the token replacement. You know Lambda’s; used to be feared and respected and known only to the Lisp/Scheme necromancers but now all the cool kids are running about like it’s Dad’s old Army overcoat.
var myContrivedExample = "<a href=\"{0}://{1}\">{2}\"</a>".format(protocol, url, text);
Because the string literal is actually an implicit instance of the String object in JavaScript, you can invoke methods on it. Very, very cool and clean.
Later on secret-agent known only as “R.” left me cryptic message to check out his implementation. But he didn’t leave a clue as how to find him, so once I mustered up the car and realized I needed few hundred more words in this post to make it seem “literate” I did a little research (Google) for “R.+String.format()” and think I found him. His post on a String.format() implementation provided both methods, a static and an instance version.
String.format = function() {
if (arguments.length == 0) {
return null;
}
var str = arguments[0];
for (var i = 1; i < arguments.length; ++i) {
var re = new RegExp('\\{' + (i - 1) + '\\}', 'gm');
str = str.replace(re, arguments[i]);
}
return str;
};
Great minds think alike. Sorry about that mate.
String.prototype.format = function() {
var str = this;
for (var i = 0; i < arguments.length; ++i) {
var re = new RegExp('\\{' + i + '\\}', 'gm');
str = str.replace(re, arguments[i]);
}
return str;
};
Thanks for posting guys.
What’s interesting to me is the variety of ways that string formatting can be implemented. I don’t know why most JavaScript frameworks don’t offer some kind of string formatting functionality out of the box. The only one that I’ve seen thus far is the Microsoft Ajax framework, but they have a whole different methodology. But that’s for a different article.