Notice This is a beta feature offered by Google. Also this is automatic translation, which means the results are often inacurate and/or hilarious. Enjoy.

ARCHIVES / RSS
Blog

JavaScript: do more with less.

h3  ~  8 Sep 2007, 20:36  –  post a comment

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:

  • be short as possible
  • be readable as possible
  • input type casting
  • output type casting

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:

  • Using sprintf have one major drawback, using the modulo (%) in a equation will break it :/
  • no type casting on the output
  • eval is used

post a comment Comments

no comments :|

Copyrighted stuff .. u know.