Thoughts on building a client-side JavaScript framework

by Kasper Tidemann

Notice: since the time of writing, I’ve appeared in the Ember Hot Seat where I talk about this and much more. Please take a look at this post in that relation.

As you may or may not know, I am the main developer of Meeho!™, a software platform for project management and client handling.

The current version of Meeho!™ is running Ruby on Rails 2.3.18 and was launched sometime during the summer of 2010, just before the launch of Ruby on Rails 3. It sported the classic initial full pageloads and the AJAX’ing of content into HTML elements using render :update. Plain, simple stuff.

Now, around April of 2011, I decided to develop and release a new version based on a client-side JavaScript framework. This was dubbed meeho.js, and it had only three dependencies, namely jQuery, jQuery Templates, and the beautiful History.js by my good friend, Benjamin Lupton. To be fair, the only hard dependency was jQuery, since both jQuery Templates and History.js could have been replaced by equivalents.

Initial thoughts

My initial thoughts for the meeho.js framework were as follows:

  1. Client-side routing and scoping
  2. Dynamic loading of templates
  3. Retrieval, saving, and caching + automatic updating of objects from the server
  4. Management of client history + application states
  5. Ability to append or remove HTML to or from the DOM according to user navigation
  6. Built-in internationalization
  7. Keyboard navigation between states
  8. Simple scaffolding for buttons, forms, and other often-used elements
  9. Management of the layout of container elements (centered, top, bottom etc.)
  10. Streamlined error handling

The meeho.js framework included a bunch of other interesting functions as well, including proper handling of times and dates to avoid the more or less broken way JavaScript handles this, support for multiple currencies etc.

The basics

At the core of the framework was the meeho namespace to which every group of sub-functionality was tied. In practice, this made it possible to view the currently-built templates through meeho.templates.stack, to build a path (and thus, initialize a state) by calling meeho.build('/some/path') or to log out the current user and revert to the login screen by calling meeho.auth.logout().

If, say, the calendar was to be shown, the framework would first check if the calendar template was loaded already. If no, it would fetch the template. If yes, it would re-use the cached version of the template.

Then the meeho.router would kick in and make sure to build the state, subsidiarily creating a meeho.templates.app.calendar object containing the calendar template, and finally call the meeho.templates.app.calendar.create() function for creating the calendar.

This resulted in the calendar appearing via an animation in the browser. And thanks to the state management backed by History.js, if the user clicked the back button, the meeho.router would kick in once again and call the meeho.templates.app.calendar.destroy() function. This would have the calendar gracefully disappear and its HTML removed from the DOM.

In the case of the calendar, the corresponding calendar.js file on the server contained the following piece of code:

create: function(graceful) {
meeho.scaffolds.leftMenuPopulate();
meeho.helpers.appAppear(this.type, this.name, graceful);
this.route(meeho.path, meeho.router.build_gracefully);
},
 
destroy: function(graceful) {
meeho.helpers.appDestroy(this.type, this.name, graceful);
}

It was not perfect since the helper functions for appearing and destroying could just take the template object itself (the this part) for further inspection – but still, I was on to something smart and it worked beautifully.

Working out the API

In order for this to really shine, the API part had to be cleverly done too. Now, on the client side, the API was accessible through the meeho.api collection of functions. For instance, it was possible to do requests via meeho.api.request(). A request could be made up of one or several queries for data to be fetched from the server.

As an example, let’s assume I wanted all the active projects associated with my user. What I would fire is the following one-liner:

meeho.api.request({"projects": {"action": "Project.get", "filters": ["active"]});

This would be handled server-side and return all active projects associated with my user in an array named projects.

Summing up the basics

So, what I ended up having developed was a way of doing things, a framework, that enabled the following behavior as an example:

  • Receive an incoming request for /spaces/calendar
  • Load the meeho.js framework and build the requested path, thus initializing the /spaces/calendar state
  • Load all required template files and logic dynamically, and then
  • load all the data to be shown for finally
  • … having the calendar appear.

By the way, in order to make this work, I had to create a wild routing scheme in my Ruby on Rails 3 backend that would redirect any request to / in order to have the meeho.js framework take it from there. My entire routes.rb file looked like this:

Meeho::Application.routes.draw do
match 'api/:action(.:format)', :controller =>; 'api', :action =>; 'handle'
match '*a' =>; 'web#index'
root :to =>; 'web#index'
end

The line in bold is what I refer to as wild routing. This approach is generally not advisable, but under these circumstances it was exactly what I wanted.

So what happened?

Yes, good question. What happened exactly? Well, for one, the entire project grew in size to a formidable amount of code to maintain and test across a multitude of platforms and browsers. If you create a client-side JavaScript framework of this magnitude all by yourself, you’re bound to spend much time maintaining and patching even the tiniest things. This I did not fully realize when I started.

Secondly, as I went on, I started googling best practices and how-to’s regarding this client-oriented approach to web development. I read articles and smartened up – way up – to the point of me asking myself: “There are so many alternatives to what you are doing out there such as Backbone.js, Knockout.js, or Ember.js – so what’s the point of developing everything from scratch yourself?”

The answer: I discovered Addy Osmani‘s TodoMVC site, a collection of implementations of a to-do list using the various popular frameworks. Looking into the code, I decided to pursue a different path than the one I was currently embarking upon.

Sometimes, in order to improve your skills, you have to let go. That is what I did.

Epilogue

It has been a journey of amazing proportions to develop my own client-side JavaScript framework. It has given me deeply rooted insight into the various challenges that reside within this development paradigm and I am thrilled to say how much I’ve learnt from starting off the hard way.

From now on, I walk hand in hand with Ember.js, my favorite framework these days. With its bindings, auto-updating of templates, and much more, it is a perfect fit for what I want to accomplish.

My thanks goes out to Yehuda Katz, Tom Dale, Peter Wagenet, and the rest of the community for their work.

And now? Now it’s back to the code – there’s much work to be done!