I like JavaScript because there is a lot of way to achieve things and finding a more efficient way to do something is always fun. Sometimes you find your self using some technique for a while and it just doesn't feel right.
Most of the time I don't have much troubles to find an elegant solution to a problem, but this one took me a while and didn't came to me while searching a solution. Probably because at the time I didn't realize that what I was doing was ugly.
In short, I often find myself writing basic "calculations" for my forms, ex.: changing the quantity will update the total price automatically. Usually I would use something like this;
$('total').value = $F('price') * $F('quantity');
While not so bad in theory, in practice it can get pretty ugly. Really ugly, if you use CSS namespaces like I do, oh and of course you have to dome some type checking:
(parseFloat($F('another-css-long-name')) * parseFloat($F('you-guessed-it-another-long-name'))) / parseInt($F(('yet-another-css-long-name'));
Well, you get the idea. My first thought was that there is way to much code to express what I want to do. So I began to think of a new syntax, from scratch.
The prerequisites where the following:
I conceded to drop the last prerequisite in order to keep the syntax as minimal as possible. I think it was the right choice. It looks like this:
$('total').value = '%d * %d'.calc('price', 'quantity');
or
'(%f * %f) / %d'.calc(
'another-css-long-name',
'you-guessed-it-another-long-name',
'yet-another-css-long-name'
);
And the most beautiful part is that this function took me 4 lines to implement in my existing library.
Object.extend(String.prototype, {
calc: function() {
args = $A(arguments).map(function(arg){ return $F(arg) || 0; });
return eval(this.sprintf.apply(this, args));
}
}
Ok, I cheated :D
Thanks to Jan and his open source sprintf function that I use to extend the string object, it saved me a LOT of code as It was already implemented in my librarY. The whole code like something like this:
Object.extend(String.prototype, {
calc: function() {
args = $A(arguments).map(function(arg){ return $F(arg) || 0; });
return eval(this.sprintf.apply(this, args));
},
// This code is in the public domain. Feel free to link back to http://jan.moesen.nu/
// ---
// Changes made by Maxime Haineault (2007):
// - The function is now extended to Strings instead (using prototype)
// - The function now accept arrays as arguments, much easier to handle (using prototype)
sprintf: function() {
if (!arguments || !RegExp) return;
var str = this;
var re = /([^%]*)%('.|0|x20)?(-)?(d+)?(.d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/; //'
var a = b = [], i = 0, numMatches = 0;
if (typeof arguments[0] == 'object') arguments = $A(arguments[0]);
while (a = re.exec(str)) {
var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4];
var pPrecision = a[5], pType = a[6], rightPart = a[7];
numMatches++;
if (pType == '%') subst = '%';
else {
var param = arguments[i];
var pad = '';
if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1);
else if (pPad) pad = pPad;
var justifyRight = true;
if (pJustify && pJustify === "-") justifyRight = false;
var minLength = -1;
if (pMinLength) minLength = parseInt(pMinLength);
var precision = -1;
if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1));
var subst = param;
if (pType == 'b') subst = parseInt(param).toString(2);
else if (pType == 'c') subst = String.fromCharCode(parseInt(param));
else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0;
else if (pType == 'u') subst = Math.abs(param);
else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
else if (pType == 'o') subst = parseInt(param).toString(8);
else if (pType == 's') subst = param;
else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase();
else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase();
}
str = leftpart + subst + rightPart;
i++;
}
return str;
}
}
But being beautiful doesn't mean being perfect, there is still three issues with this code that bugs me:
no comments :|