Writing Desktop Class Applications in JavaScript
Thanks go to Ross Boucher, founder of 280North, for reading an earlier version of this article and correcting a few errors.
The web has a spectrum of sites that fall between static documents and applications. On the document end is cnn.com, and the application end is Gmail and MobileMe. If I sorted popular sites on a spectrum:
DOCUMENTS
---
CNN.com
Craig's List
Flickr
eBay
Amazon
Digg
Netflix
Twitter
Youtube
Pandora
Google Maps
Basecamp
Gmail
Facebook
MobileMe
---
APPLICATIONS
When a site successfully enters the application space, the bar is raised. Thanks to Gmail, you’d be insane to launch a webmail service that behaves like Hotmail in the 90’s.
A few years ago, we said web 2.0 sites had “ajax.” The next segmentation will be between between semi-dynamic sites such as Digg and Flickr, and desktop like web apps such as 280Slides and MobileMe, so pardon the digression, but what do we call them?
“Rich Internet Applications” sounds right, but it’s become a euphemism for Flash-only sites with horrible user interfaces and funny looking menus. Then again, “rich” sounds like marketing, positioning document-oriented websites as “bland.”
Lately I’ve been using “Desktop Experience.” When you think desktop, you think of something solid. When you double click somewhere, you expect an immediate response– and highlighting all the text on the page doesn’t count. But maybe I like “desktop experience” because “dex” sounds alright.
Sorry. Moving on…
The Need for Frameworks
The web was designed for documents. Canvas, CSS animation, drag-and-drop and other HTML5 APIs allow a richer experience, but these are APIs, not frameworks. Frameworks are about packaging solutions to common design problems.
Desktop applications use the Model-View-Controller pattern to separate code into content, presentation, and interaction. Ruby on Rails sold MVC to web developers, but treats “view” as both html and JavaScript; click handlers and DOM manipulation exist alongside remote calls and form validation.
Worse, most web apps update their views via callbacks. Clicking a button triggers a callback to perform an action and then update view code. Callbacks become unwieldy as the interface gets smarter.
Imagine you were building iPhoto. Clicking a photo in a list should enable or disable buttons in the toolbar, depending on the context.
You might put a click handler on photo selection:
function onPhotoClicked(photoIndex){
updateCurrentSelectionTo(photoIndex)
updateToolbarButtons()
}
Then you realize you need to cover photo deletion:
function onDeleteButton(photoIndex){
deleteOnServer(photoIndex)
deletePhotoInList(photoIndex)
updateToolbarButtons()
}
As you build up your UI, these updateToolbarButtons() get littered among unrelated code, and things get brittle.
Desktop frameworks like Cocoa solve this with “observers.” You can ask the framework to run a method when an object’s attribute changes. In this case, we would create an observer that watches “selection” and triggers updateToolbarButtons().
“Bindings” are even more elegant: you can ask the framework to keep the disparate objects’ attributes in sync. You can bind a text field to a photo caption, and the framework handles the glue. No need to worry about circular callbacks.
Finally, desktop applications feel different than documents. Toolbars stay in fixed positions, and panes are resizable. Many popular ajax library get close, but frequently have minor inconsistencies that break the illusion. Desktop frameworks provide consistent widgets that operate on a high level, and the tools to develop your own through subclassing or mix-ins.
Avoid Proprietary Platforms
Standardization is slow, so proprietary platforms are the first to solve problems. Flash, Silverlight, and JavaFX offer desktop like frameworks but carry technical flaws: the iPhone doesn’t support them, vendors may break things at any time, and the runtimes are often slower than JavaScript.
There is also a conflict of interest in making a platform free but selling tools. It’s the same criticism against open source business models: if you make a platform simple, why will they buy your tools?
Evaluating Frameworks
Cappuccino
I decided to investigate a few frameworks. The first was Cappuccino, the most impressive technical accomplishment. Cappuccino was created by 280North, the company behind the impressive presentation web app, 280Slides.
Cappuccino implements Cocoa in JavaScript, and layers a new language on top of JavaScript called “Objective-J.” In JavaScript, you might write:
player.start()
While in Objective-J you would write:
[player start];
Though it’s ultimately compiled down to JavaScript. On a technical level, this second runtime allows things like look ahead downloading, and method_missing style dispatch. Your first question may be, “How does this complicate debugging?” To solve that problem, the Cappuccino team made commits to webkit to solve the problem, and make life easier for all framework developers.
On a social level, Objective-J opens up the Cocoa ecosystem; existing Cocoa developers can leverage their experience, while new developers read Cocoa tutorials to learn patterns and terminology. Tools like “nib2cib” can convert Interface Builder nib/xib files into Cappuccino’s equivalent .cib, though it only works on a subset of classes.
Diving into the project, I was amazed by Cappuccino’s parity with Cocoa. I could skip documentation and rely on my iPhone development experience. However, a day into the project, the abstraction leaked. Cappuccino’s view framework asks you to pretend css and html don’t exist. Like Cocoa, you specify layouts with functions like CGMakeRect(). The brick wall was trying to load a SWF.
(Why would I require Flash support when earlier I swore it off? For now, Flash is required for things like YouTube videos. More importantly, integrating flash is an annoyance that tests a framework’s “backward compatibility.”)
Cappuccino provided “CPFlashView,” but doesn’t support flash variables. After much frustration and wasted time, I settled on a hack of accessing private variables and methods. It was a hideous, and could break at any time, which was entirely the problem I was avoiding by using a framework. Luckily, flash variable is sitting on Cappuccino branch, waiting to be merged in.
Finally, having used both JavaScript and Objective-C in production, I feel Objective-J offers no technical or aesthetic advantages over plain old JavaScript. It’s a lateral change that offers no benefits, but huge risk of breakdown. However, my opinion should change as new features emerge only possible via the runtime.
Sproutcore
SproutCore was developed by the company SproutIt for their webmail application. It shot to fame when Apple chose it as the frontend to Me.com.
SproutCore takes the opposite approach of Cappuccino. Everything is plain old JavaScript. Custom views require HTML and CSS.
To introduce observers and bindings without adding a layer above the JavaScript, SproutCore makes one concession: you must use .get() and .set() to read and modify values on your objects. Those methods ensures updates propagate.
I feel like SproutCore was born out of production use. Take the code generators: not only do they generate skeletons, but your models include unit tests and fixtures. The latter lets you load dummy data into your app so you can prototype your UI before building a backend.
Another advantage of SproutCore is straightforward view design. Cappuccino uses frames and an auto-resizing mask.
view = [[CPView alloc] init];
[view setFrame:[[view superview] bounds]];
[view setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
Whereas Sproutcore would use:
view = SC.View.design({
'layout':{top:0, right:0, bottom:0, left:0}
});
Or consider this snippet from the Cappuccino source:
_cancelButton = [[CPButton alloc] initWithFrame:CPMakeRect(frame.size.width - 27,(frame.size.height-22)/2,22,22)];
The equivalent in SproutCore:
_cancelButton = SC.ButtonView.design({
layout:{right: 27, centerY: 0, width:22, height:22}
})
It’s also easy to switch to static layouts. This is perfect for content that varies in size (e.g. chat logs), because they’re a pain to lay out with a Cocoa view model. I inspected the source to a Cappuccino app that displays tweets, and it contained some pretty bad hacks to calculate view dimensions.
SWF loading wasn’t documented, but easy to figure out– a recurring theme, as SproutCore lacks documentation. The wiki was incomplete, and sometimes just wrong. When the blog changed domains nobody moved old articles, so I relied on the Google Cache for important release notes.
You can blame the rapidly changing API leading up to 1.0. The SproutCore team would rather annoy early adopters than live with blemishes. However, since announcing the 1.0 release candidate the wiki has significantly improved, and I’m finding more SproutCore bloggers.
Conclusions
Both frameworks are lightyears ahead of existing JavaScript frameworks. They’re also approaching parity, with Cappuccino working on bindings and SproutCore working on its own in-browser Interface Builder.
However, in Cappuccino I got a sense of cargo culting. Convention includes providing return types, initializing through “alloc” and “init”, and generating layout objects using CPMakeRect.
Or take namespacing. Sproutcore attempts to namespace, while Cappuccino follows Cocoa conventions and prefixes names. It’s “SC.View” vs “CPView”. While the first reads clearer, the Cappuccino team rebuts that it isn’t really a namespace, and the extra object lookup affects IE6 performance.
Cappuccino follows Cocoa down to the syntax, while SproutCore follows the Cocoa spirit. My opinion may be different if I had a decade of Cocoa development behind me, but the latter feels more pragmatic.
Since beginning this post, I wrote a webchat system using Sproutcore, powered by EventMachine, Tokyo Tyrant, and the nginx http_push module. With gzip enabled, the app is only 112k. It works great in Internet Explorer, FireFox, and Webkit. I’ll explore the code in a future post.