May 13, 2008

JavaScript Date Helpers

Listen to this articleListen to this article

It's all the rage these days to have timestamps displayed in words to indicate how long ago some event occurred. You know something like "less than a minute ago" or "about 2 months ago", etc. You'll see plenty of examples on news sites, blog entries, and bug tracking tickets to name but a few.

If you've ever had to build this kind of thing in Rails you'll be familiar with all the Date Helper methods that make the task pretty trivial. The problem is that the result is fixed to whatever the date was when the page was rendered and as a result these timestamps go stale very quickly.

Save refreshing the page every minute--or hour or whatever--just to update the times, I figured what was needed was a little but of client-side action. Rather than send the text in the HTML, I decided to instead send the raw timestamps and have the browser periodically generate the textual representation.

To this end, I blatantly copied two methods from the afore-mentioned Rails helper-- distance_of_time_in_words(from, to), and time_ago_in_words(from)--and, taking some liberties along the way, converted them to JavaScript:

  distance_in_milliseconds = to - this;
  distance_in_minutes = Math.abs(distance_in_milliseconds / 60000).round();

  if (distance_in_minutes == 0) {
    words = "less than a minute";
  } else if (distance_in_minutes == 1) {
    words = "1 minute";
  } else if (distance_in_minutes < 45) {
    words = distance_in_minutes + " minutes";
  } else if (distance_in_minutes < 90) {
    words = "about 1 hour";
  } else if (distance_in_minutes < 1440) {
    words = "about " + (distance_in_minutes / 60).round() + " hours";
  } else if (distance_in_minutes < 2160) {
    words = "about 1 day";
  } else if (distance_in_minutes < 43200) {
    words = (distance_in_minutes / 1440).round() + " days";
  } else if (distance_in_minutes < 86400) {
    words = "about 1 month";
  } else if (distance_in_minutes < 525600) {
    words = (distance_in_minutes / 43200).round() + " months";
  } else if (distance_in_minutes < 1051200) {
    words = "about 1 year";
  } else {
    words = "over " + (distance_in_minutes / 525600).round() + " years";
  }
  
  return words;
};

Date.prototype.time_ago_in_words = function() {
  return this.distance_of_time_in_words(new Date());
};

Now all I do is periodically invoke a function that calls one or other of these methods and updates the text of whatever display element is appropriate. Even better, because the raw timestamps have timezone information in them, the display doesn't suffer from, in my case here in Australia, always being 10 hours out because the server is sitting in the US with a US date/time.

Giving the Anchor tag some Ajax Lov'n

Listen to this articleListen to this article

It seems I'm forever needing to submit links using an XMLHttpRequest rather than the default full-page refresh. One approach commonly used in the Rails community is to render each link with the JavaScript already in place. My preferred approach however is to keep the HTML as free from JavaScript as possible and unobtrusively add behaviour using LowPro.

LowPro already comes with a built-in behaviour for links but sometimes I need something little more complex than simply submitting the request and so I usually end up doing the following:

anchor = ...;
new Ajax.Request(anchor.href, { method: "get", parameters: ... });

Granted that's not a lot of effort but it still felt as though I were repeating myself and that the overall intention of my code was largely obscured by the infrastructure. It then struck me that submitting a form using the Prototype JavaScript framework is almost trivial:

form = ...;
form.request({ parameters: ... });

So I cooked up a version for anchors as well:

Element.addMethods("A", {
  request: function(anchor, options) {
    new Ajax.Request(anchor.href, Object.extend({ method : "get" }, options || {}));
  }
});

Now I can submit links in pretty much the same was as I do forms:

anchor = ...;
anchor.request({ parameters: ... });

I'm wondering what other possibilities might occur were I to add a serialize() method to extract the request parameters.

April 14, 2008

Getting Too Fancy with HTTP Response Codes

Listen to this articleListen to this article

As part of my adoption of REST and all its goodness, I've started using HTTP response codes more, responsibly ;-) So, for example, instead of always returning 200 (OK) for just about everything, I'm using 201 (Created) with a Location header set to the new URL after a POST. For PUT, I send back 204 (No Content), a 404 (Not Found) after a GET for a resource that no longer exists, and a good old 200 (OK) after a successful DELETE or GET.

Interestingly, in the system I'm developing at present, an update (PUT) might actually cause a resource to move due to the application of server-side business rules. In this case, the 204 response also sets the Location header so that the client knows where it can be found.

All this was working beautifully on my local machine using both Safari and Firefox so once I was happy with the result I deployed it into the remote test site and started playing in FireFox. So far so good. Everything checks out. Next let's try Safari...not so great.

Some bits of the application worked just fine but others seemed to have no effect. Then mysteriously things would start working again. Even stranger was the fact that hitting the browser's refresh button had no effect either.

At first I suspected that nesting Ajax calls might be to blame but as everything seemed to work perfectly in FireFox and a Google search turned up nothing, I decided to do some more investigation.

I logged in to the server box and tailed the logs for signs of life. Everything looked normal. All the expected requests and responses were there but still nothing client side. Using Safari's new Network Timeline I could see what the browser thought was going on. All the requests and responses were there but something was odd. In all but a few cases, the response code was 204 (No Content). I double checked the server logs but no, the server was definitely sending back the correct responses; a mixture of 204, 200 and 404 as appropriate.

On a hunch I went back and re-read the HTTP Status Codes document and in particular the definition for 204:

The server has fulfilled the request but does not need to return an entity-body ... the client SHOULD NOT change its document view from that which caused the request to be sent ...

That might actually explain it. If Safari received a 204 and interpreted that to mean "Don't change anything" then hitting refresh would indeed have no effect even if my code subsequently went on to perform more asynchronous requests as a result.

So, I dutifully changed all the 204s to 200s and voila! Safari started to behave just as expected and FireFox continued to work as had previously.

I've also noticed a difference in the way both browsers handle 303 (Redirect) from within an XML HTTP Request: Safari performs the redirect and keeps all the headers as per the original, whereas FireFox seems to essentially construct an entirely new request. The upshot is that you can't actually detect (server-side) an XML HTTP Request from FireFox if it is the result of a redirect.

I'm really not sure why the two browsers have such differing opinions of what the appropriate behaviour should be in either case but I hope this helps some other poor sod keep from pulling their hair out.

April 11, 2008

Drag & Drop Prioritizable Lists

Listen to this articleListen to this article

Yes, it's true, Scriptaculous already provides a Sortable that makes it almost trivial to enable drag'n'drop sorting of your HTML lists. Whenever an item is moved an onUpdate() event is called (if provided) allowing you to inspect the new order and presumably perform an AJAX request to record the change. In principle, this sounds great but I've never really liked it for a couple of reasons.

For a start, if you have any appreciable number of items updating each in the database just to re-order one seems somewhat unnecessary. Not withstanding the fact that we need to send all those ids to the server in the first place.

Secondly, if you're doing any kind of filtering, it's difficult at best to take the newly constructed ordering and apply that at the back-end; what happens to all the items that may be lurking in between that aren't presently displayed?

Enter Prioritizable (itself built on top of Sortable).

You use it in much the same way as Sortable with the major difference being that the onUpdate() event is called with three arguments: the item that was moved, the sibling relative to which it was moved, and the relative position ("higher" or "lower"). And, if like me, you're feeling a bit RESTful, it's pretty easy to turn these arguments into a nice semantic URL and parameters as shown:

Prioritizable.create($("chores"), {
  onUpdate: function(item, position, sibling) {
    id = item.substring(6);                           // "chore_17" => "17"
    sibling_id = to.substring(6);                     // "chore_2" => "2"
    url = "/chores/" + sibling_id + "/" + position;   // "/chores/2/higher"
    
    new Ajax.Request(url, {
      method: "post",
      parameters: { id: id }
    });
  }
});

When the onUpdate() event is called we POST the id of the item to be moved to a path constructed from the id of the sibling and the relative position. Assuming the the user moves chore_17 just above chore_2 we would POST "id=17" to /chores/2/higher.

In practice, I combine this client-side behaviour with some server-side code that provides move_higher_than() and move_lower_than() methods that efficiently handle all the necessary database updates.

All the pieces mentioned will eventually be available alongside Cogent's other Rails plugins but until then, here's enough of the Javascript side of things to get you going.

var Prioritizable = {
  create: function(element) {
    options = Object.extend(arguments[1] || {}, {
      onChange: Prioritizable.onChange,
      onUpdate: Prioritizable.onUpdate  
    });

    Sortable.create(element, options);
  },

  destroy: function(element) {
    Sortable.destroy(element);
  },

  onChange: function(item) {
    Sortable.options(item)._item = item;
  },

  onUpdate: function(element) {
    options = Sortable.options(element);
    item = options._item;
    options._item = null;

    sibling = item.previous();
    if (other) {
      position = "higher";
    } else {
      sibling = item.next();
      position = "lower"
    }
    
    options.onUpdate(item, position, sibling);
  }
};

Enjoy!

April 02, 2008

Fixing lowpro form submission failures

Listen to this articleListen to this article

I finally worked out why my forms weren't submitting when the user hits the ENTER key. lowpro serializes the button that was clicked along with any other parmeters when submitting a form via AJAX. However, when the user hits enter under FireFox, there is no button and consequently the browser barfs. Safari on the other hand tries to be too helpful and triggers an onclick event for the first submit button (which is why I never noticed it).

So anyway, rather than try to be too clever myself, I simply changed the parameter serialization in Remote.Form.onsubmit to look like:

parameters : this.element.serialize({ submit: this._submitButton ? this._submitButton.name : null })

Problem solved.

March 19, 2008

Culturally Sensitive JavaScript

Listen to this articleListen to this article

JavaScript is a fantastic little language and with the likes of Prototype, Scriptaculous, and my newest favourite, lowpro, you can build some quite frankly, remarkable web applications.

One area where most web browsers fall down however is in their error-reporting, or lack thereof. A fact that has caused me to waste seemingly countless hours trying to find the source of some problem or other only to realise that a typo that had been staring me in the face the entire time was to blame!

Now, like just about any programming library I use these days, most JavaScript libraries use American english. initialize, capitalize, you know what I'm talking about.

For the most part the use of 'z' instead of 's' isn't too much of a problem but just recently I consistently tried to use lowpro's addBehaviour method, only there isn't one. It's called addBehavior (sans 'u').

So today after about 20 minutes cursing and swearing at the spelling Steve asked "is there anyway you could create an alias?" Being JavaScript the answer is of course "abso-bloody-lutely!":

Event.addBehaviour = Event.addBehavior

You can alias just about anything this way.

No more will my code silently fail due to differences in spelling :)

December 04, 2005

Dojo or Prototype or ...?

Listen to this articleListen to this article

I'm about to start adding "funky" Look & Feel stuff to my Rails app and I was wondering what the general consensus (if there even is such a thing) is on JavaScript libraries.

I really don't care so much about the ease or otherwise of making XMLHttpRequest calls; this is the least of my worries—it's trivial to do manually. What I'm more interested in are things such as Drah & Drop, Accordians, Select-as-you-type, and other more general DHTML layout stuff.

Rails itself ships with prototype+scriptaculous which I'm lead to believe are quite good. My mate Andy on the other-hand has played a bit with Dojo and likes that too.

Anyone care to share their own experiences?

September 19, 2005

ActionScript: JavaScript in Flashy Pants?

Listen to this articleListen to this article

One of the jobs I'm doing at the moment involves Flex—and consequently Flash—for the front-end and Java at the back-end, most of which involves writing ActionScript.

If you haven't used ActionScript before, you'd be forgiven for thinking you were writing JavaScript. Well, in fact you are writing JavaScript with a few changes, not necessarily for the better IMHO—If you read the documentation you'll discover that ActionScript is indeed based on ECMA Script, the same specification that was retro-fitted to JavaScript.

From what I can tell, ActionScript 1.x was pretty much exactly JavaScript. Version 2 on the other-hand adds some syntactic sugar to the language; no doubt an attempt to make a prototype-based language appear more like a class-based language in the hope of attracting all the Java developers. I'll get into these additions in a bit but in any event, I find it highly amusing when Macromedia proclaim that version 1 was somehow a functional language and that version 2 is now a fully-blown O-O language.

(As an aside, I interviewed some potential developers the other day who said they loved ActionScript but thought JavaScript was a language for doing hack-work. I can only assume that opinion comes from having only used JavaScript for handling onXXX events in a browser rather than any objective comparison of the langauges themselves.)

So, on to some of the more notable changes to the language starting with type-safety. JavaScript is weakly typed. This is a topic of biblical proportions so I'll leave alone the merits or otherwise of weak-typing suffice to say that I like it. In their infinite wisdom, Macromedia decided that what was needed was a bit of strong (or strict) typing. This is enforced by the compiler with a combination of type declaration after variable and parameter names:

var foo : Number;

Unfortunately this type-safety is limited to compile-time checking—nothing happens at runtime, much like Java's generics. Not so much of a problem I guess—I didn't want the strong typing in the first place—but amusing nonetheless.

So the next question is, how do you know what functions are available for a given type? In JavaScript these are essentially defined at runtime by adding behavior to a prototype. Well why not add some more syntax to the language?

class MyClass {
    private var num : Number;
    public MyClass(num : Number) {
        this.num = num;
    }

    public getNum() : Number {
        return this.num;
    }
}

Ok, so that's probably a little easier for most people to understand than:

MyClass = function(num) {
    this.num = num;
};

MyClass.prototype.getNum = function() {
    return this.num;
};

But I actually find the "new" way actually involves a lot more typing than the good-old-fashioned way—which once you're used to is easy to read anyway—and hides the fact that it is still a prototype-based language and NOT a class-based one. (I also quite like the way prototype declares "classes" but I haven't found the time to start doing it that way just yet.)

You can also declare property accessors just like in VB/C#/etc:

class MyClass {
    private var num : Number;
    ...
    public get num : Number {
        return this.num;
    }

    public set num(num : Number) : Void {
        this.num = num;
    }
}

Again, great if you like that kind of thing; I'm still not convinced I do.

The other thing that gets people is the scoping rules. JavaScript has some pretty funky scoping rules which once you're used to and understand work just fine. Again, because ActionScript looks like Java, developers don't realise the importance of understanding the scoping rules.

ActionScript also introduces another new keyword: dynamic. This can be used when defining a class and indicates to the compiler that it should NOT do strong type checking when invoking methods and accessing properties of the class—either from within or from outside the class. Again, I find this a highly amusing construct as I can always defeat the type checking the old-fashioned way anyhow: myObj["doSomething"](15);. Yes, I agree, that's a bit of malicious code but what I'd prefer is an option to turn on/off strict typing at a project level and not on a per-class basis.

Interestingly, someone recently pointed me at an open source compiler for ActionScript. I wonder if I could just download the code, remove the strict type checking and be done with it ;-). Oh and there's also a unit testing toolkit as well.

All in all, I quite like using ActionScript. I've only played around a bit with Flash/Flex bindings but they seem quite nice too. I actually think it would be pretty easy to build a Cocoa—JavaBeans the way they should have worked—like framework (eek there's the word!) which I thoroughly enjoy using at the moment.

But when it comes down to it, It has been my experience—and no doubt the experience of others in the JavaScript/Ruby/Smalltalk/Objective-C world—that I can achieve a lot more in fewer lines of readable code without all the syntactic sugar and strong typing. (The caveat being that I'm also a big fan of automated testing.) Moreover, understanding that JavaScript uses prototypes opens up a whole new world of possibilities that further increase my productivity—a fact that became apparent to the attendees of a mini presentation I gave on this subject just recently.

My Biggest Peeves With JavaScript

Listen to this articleListen to this article

There's really not a lot to dislike about JavaScript; It's object oriented—in a way Java can only dream of; weak-typing coupled with dynamic property objects make mocking and testing a breeze; the list goes on. But there are two things about the language that continue to irritate me: Exceptions; and undefined/null. Ok, so I guess that's three things but the last two are really in the same category.

First up, undefined/null. Let me start by saying that I love the fact that these are actually objects—go null object pattern— but the fact that they silently gobble up any calls made to them is less than helpful. Even less helpful is the fact that I can't override this behaviour. Unlike every other object in the language, these two don't seem to have a prototype I can mess with. At least in Objective-C/Smalltalk you have the option of finding out when messages are sent to null objects or to objects that don't understand them. Thankfully, testing catches most of these problems but where it can't, I resort to my tried and tested method: runtime assertions. Which leads me to my second peeve.

Exceptions. Well at least JavaScript has them I guess, even if they are called Errors. Sure I can throw them, I can catch them, I can put code in a finally block but I can't get any information about them. I use runtime assertions all the time in Java, Objective-C and even JavaScript to catch things I haven't managed to test for or more importantly, things I can't necessarily predict. Whenever an assertion fails, an exception is thrown which usually dumps a stack trace giving me everything—well almost everything—I need to track down the source of the problem. Unfortunately in all web browsers I use regularly—Safari and Firefox—all I seem to get is the message "Error" printed to the JavaScript console. Great! So I know there is a problem but I don't know where, nor importantly why? This usually leads me to the JavaScript debugger—probably the best thing that ever happened to the language. Not that I mind using the debugger but a stack-trace is extremely useful!

August 22, 2005

Scraps of JavaScript

Listen to this articleListen to this article

Not much today but some little bits-and-pieces of stuff I've picked up over the last two weeks. It's been a steep learning curve going from no JavaScript to writing a character-based terminal emulator and it's sure been fun.

Now that I have a modicum of JavaScript under my belt, I think I'll finally take Big Daz' advice and have another look at prototype. I had a quick look initially—on his recommendation—but I was so new to the language that none of it made much sense. FWIW, thanks again to Big Daz, I also spent a lot of time reading quirksmode.org.

Overall, DHTML works really well. The browsers seem to handle running JavaScript pretty well -- the performance is quite impressive—and it's not that difficult to get things to work cross-browser.

So, here we go...

Rather than report an error, most browsers seem to silently fail or at best give a rather less than helpful message -- either by way of a pop-up or a message to the JavaScript console.

The error messages in Mozilla—sent to the JavaScript Console—are far more useful than those generated by Safari -- also sent to the JavaScript Console; MSIE is woeful when reporting (by way of a pop-up) errors in JavaScript files that have been included via <script language="javascript" src="..." type="text/javascript" />.

The debugger for Mozilla works a treat.

Methods can't be named the same as fields—they're really just the same thing anyway. Not really a problem but I was translating some code to JavaScript and it didn't work out as I had planned ;-). Either use an underscore (_) for field names; make sure your method names are always prefixed with a verb such as get/is/etc.; or "allow" direct access to fields. I say "allow" because strictly speaking, it seems that field values are pretty much always accessible anyway.

Closures usually require that you define a variable with a value of this to ensure you can always refer back to the object that owns the function being called:

    var self = this;
    orders.each(function(order) {
        self.process(order);
    });

To ensure your onkeypress event handler is called with an event object, use something like the following to capture the event and then delegate:

    var self = this;
    document.onkeypress = function(event) {
        return self.onkeypress(event ? event : window.event);
    }

To have a keystroke ignored seems to require the following code in your onkeypress event:

    event.cancelBubble = true;
    event.returnValue = false;
    return false;

This works for most everything with the noteable exception of F1 in MSIE which displays help on the browser. To prevent this, try:

    document.onhelp = function() {
        return false;
    };

The Mac generates very odd key codes for things such as Up (63232), Down (63233), Left (63234), Right (63235), etc. I say odd only because I'm used to the ones generated on PCs (38, 40, 37, 39, ...). Ok, so maybe they're not odd just different ;-)

MSIE seems only to allow you to modify the content (DHTML) of a div.

Even though the HTTP protocol allows you to send and receive binary data -- using Content-Type: application/octet-stream and Content-Transfer-Encoding: binary for example --- none of the browsers I tested would reliably allow the JavaScript code to receive that data as a string of characters, even though the browser would quite happily download the content to a file on my hard-disk and allow me to manually construct a string with identical content -- using String.fromCharCode(0x1b) for example.

You can simulate Swings invokeLater by using window.setTimeout() with a time-out value of zero:

    var self = this;
    window.setTimeout(function() {
        self.doSomething(...);
    }, 0);

Most of the browsers I tested didn't seem to support for .. in ..; they all accepted the syntax but produced kooky results when used.

All browsers I tested support using innerHTML to replace the content:

    document.getElementById(id).innerHTML = html;

Using a span with CSS classes is the simplest way to inline style changes:

    <span class="important">...</span>

Handling errors (and for that matter state changes) when using XMLHttpRequest (or in the case of MSIE, ActiveXObject("Microsoft.XMLHTTP")) differs between browsers:

  • Safari and MSIE seem to always set request.status and request.statusText;
  • Netscape/Mozilla seem to sometimes set these variables, yet other times throw exceptions due to the varible having not been defined;
  • Most will allow any old value for request method and URL and notify you via onreadystatechange if there was an error -- such as 404 Not Found for example -- though sometimes (under what circumstances I don't recall) they will throw an exception on open() and sometimes on send().

Both Netscape/Mozilla and MSIE append a CRLF (0x0d0a) to the end of any content you send, leaving the Content-Length field two-bytes short; Safari seems to leave the content as-is. Not really a problem but interesting as the data already had the CRLF as usually recommended for sending content via HTTP.

To change the colour of a horizontal-rule (<hr class="a_style" />) in a browser-neutral manner, you need to set your CSS style as:

hr.a_style {
    background-color: #NNNNNN;
    color: #MMMMMM;
    border: 0;
    height: 1px;
}

You can call a method using a string for the name, allow a switch-like calling mechanism:

    var methodName = (this.insertMode) ? "insert" : "overwrite";
    this[methodName](aCharacter);

More to come I'm sure. Add any more you can think of or let me know of better ways to do these things as I'm truly ignorant in this space.

Shameless Plugs

Recommend me on Working With Rails

Simian (Similarity Analyser): Rapidly identifies duplication in Java, C#, C, C++, COBOL, Ruby, JSP, ASP, HTML, XML, SQL, Visual Basic source code and even plain text files.

Beginning Algorithms: A good understanding of algorithms, and the knowledge of when to apply them, is crucial to producing software that not only works correctly, but also performs efficiently.

Blogroll

Creative Commons License
This weblog is licensed under a Creative Commons License.

Powered by
Movable Type 3.2