Tuesday, January 05, 2010

Why I left GWT and came crawling back to jquery

jquery, I'm sorry I said those mean things about you. After all the good times we had together, I walked away the moment GWT batted its eyes at me.

We got along okay at first, GWT and me. But then I started running into problems with the simplest things due to GWT's convoluted overuse of container markup for things like text and links. Trudging on, I encountered vexing UI layout bugs that could not easily be solved without dissecting that very-same convoluted container markup. When I cracked open the Firebug Inspector, I was shocked to see just how many containers were being created for even the simplest layouts, and that same complexity made it mind-numbingly tedious to comprehend the effects of the CSS on all that markup.

Of course, using Eclipse's superior source-code navigation capabilities, I was able to find the offending source code. It was one of those weird decisions made in a private method 4 superclasses up. (To fork, or not to fork? That is the question.) With the offending code safely barricaded, er, encapsulated, I turned back to hacking the CSS rules on that seemingly unending hierarchy of containers. That was when I first doubted my decision to leave you, jquery... But I trudged on, in search of that beautiful, statically-typed, generic, polymorphic sunrise.

The two faces of GWT-RPC

Okay, so the approach to UI layout is needlessly complex and tedious, but at least there is the beauty of GWT-RPC, right? That's what I thought, until I learned firsthand of the nasty RPC problems that arise when you make assumptions about what objects you can transfer. There are JavaScriptObjects and pojos and a bloodbath of DTOs trying to hold things together.

There was the time I naively assumed that I could read a JavaScriptObject from the "wild" and then pass it upstream in a GWT-RPC call. BZZZTT! Please convert to a pojo and try again.

Then there was the dreaded RPC Types Whitelist thingie that couldn't deal with my clever use of generics. I couldn't find this mythical whitelist file, so I just added dummy methods to my RPC interface referencing all the different concrete types I would need to be able to handle. (Bleh.)

Closures

There was also the misery of recreating in tedious boilerplate Java what is so succint and beautiful in Javascript. Closures become anonymous callback classes with several lines of boilerplate for the interface instantiation and the method declaration. I would agonize about whether to convert to a top-level class, which would then require me to pass in all my closure variables to a constructor and hold them in member variables, and pretty soon the once-elegant javascript closure has exploded into a 100-line .java file of its very own...

Java has a degree of elegance when compared to C++, but not next to Javascript: closures save time. Hip companies like Apple know this. Maybe someday Sun will catch up.

Here's your host.... (loading)

Then there's GWT hosted mode with its slow launch time and its generated code that I can't follow in Firebug because, you know, some-really-smart-guy-that's-probably-smarter-than-me wrote it, and so what right do I have to debug code generated by said-smart-guy's compiler? But then there's this little problem called, I have no idea why my RPC is failing, and why does it have to be this hard? and maybe the GWT-RPC mechanism is just flawed, and maybe I should just dump it and rewrite my own RPC mechanism?

Dump GWT-RPC and rewrite my own RPC mechanism? Not a bad idea....

And so I did.

Using jquery.

JSON, JSOFF

The funny thing is, it was so easy. All I needed was some JSON conversion on the server-side and then your getJSON() method took care of everything else for me. And then finally, I had my Firebug back. And I had my closures back. And no more private methods 4 superclasses up; I had my easy mixins back. And suddenly event-handling was easy again.

I miss a few things from that fling with GWT. The CSS sprites were good (ImageBundle, in GWT parlance). The idea of deferred binding is nice. The focus on performance in general, will be missed. The new native hosted mode is definitely a step up from GWT 1.x. I will miss refactoring, sort of. (It's absolutely essential when dealing with the verbosity of Java, but many of the types of code changes that make refactoring necessary in Java just aren't even necessary in Javascript.)

Still, it's good to be back. It's amazing what you can do in just a few lines with a javascript closure. And I don't mind writing "throwaway javascript code" when it feels good every time.













Wednesday, December 16, 2009

IBM Acquires Lombardi

Lombardi, my employer, announced this morning that they have been acquired by IBM.

Monday, July 06, 2009

Tweaks to Zach's humane Date script

I like Zach's humane Date script (based on work by Dean Landolt, John Resig, and henrah), but I wanted to be able to pass in an actual Date object in addition to just an ISO8601 date string. I also wanted it to be able to handle future dates (e.g. "10 minutes from now"). Here's the result:
 
/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 * 
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification 
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 * 
 * Licensed under the MIT license.
 */

 /**
  * @param {String or Date} date_str either an ISO8601 date string or a Date object.
  *          Note: ISO8601 dates are always formatted using UTC/GMT timezone.
  *          Example: 2009-06-03T20:06:44Z
  */
function humane_date(date_str){
 var time_formats = [
  [1, '1 second'],
  [60, 'seconds', 1],
  [90, '1 Minute'], // 60*1.5
  [3600, 'Minutes', 60], // 60*60, 60
  [5400, '1 Hour'], // 60*60*1.5
  [86400, 'Hours', 3600], // 60*60*24, 60*60
  [129600, '1 Day'], // 60*60*24*1.5
  [604800, 'Days', 86400], // 60*60*24*7, 60*60*24
  [907200, '1 Week'], // 60*60*24*7*1.5
  [2628000, 'Weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
  [3942000, '1 Month'], // 60*60*24*(365/12)*1.5
  [31536000, 'Months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
  [47304000, '1 Year'], // 60*60*24*365*1.5
  [3153600000, 'Years', 31536000], // 60*60*24*365*100, 60*60*24*365
  [4730400000, '1 Century'], // 60*60*24*365*100*1.5
 ];

 var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
  dt = new Date,
  seconds = (date_str instanceof Date ? (dt - date_str)
              : (dt - new Date(time) + (dt.getTimezoneOffset() * 60000))) / 1000,
  token = ' ago',
  i = 0,
  format;

 if (seconds < 0) {
  seconds = Math.abs(seconds);
  token = ' from now';
 }

 while (format = time_formats[i++]) {
  if (seconds < format[0]) {
   if (format.length == 2) {
    return format[1] + token
   } else {
    return Math.round(seconds / format[2]) + ' ' + format[1] + token;
   }
  }
 }

 // overflow for centuries
 if(seconds > 4730400000)
  return Math.round(seconds / 4730400000) + ' Centuries' + token;

 return date_str;
};

if(typeof jQuery != 'undefined') {
 jQuery.fn.humane_dates = function(){
  return this.each(function(){
   var date = humane_date(this.title);
   if(date && jQuery(this).text() != date) // don't modify the dom if we don't have to
    jQuery(this).text(date);
  });
 };
}