I recently built a fairly rich web application from the ground up. Whilst I’ve being building web apps since the mid-nineties this little project had a different flavour to it and I thought I’d go over my learnings here.
Firstly why was this project different:
- its a Facebook app and uses their JavaScript SDK. This means that 99% of the app runs client side with only a small bit of server side code.
- I’m was the sole developer. Normally I work with 2 or more other developers, focus on the back-end work and leave the bulk of front-end stuff to others
- rapid development approach. The app was built quickly with me working closely with a UX guy and a designer. Whilst the functionality was locked down early on, the user interface and interactions evolved rapidly and iteratively.
First observation: its so easy
OK that is slightly facetious, but modern CSS and JavaScript frameworks mean that you don’t have to be guru to build nice looking, smooth functioning, cross browser applications.
For this application I used the Twitter Bootstrap CSS framework and the jQuery Javascript framework.
Get a designer
Yes I know I just said that it’s easy and you don’t need to be a guru to create nice looking apps BUT whilst Twitter Bootstrap encapsulates a great design, you are still going to want to customise it.
Designers have the knack of adding the odd image, drop shadow or bit of whitespace that can make a perfectly ok looking web page just suddenly pop. Not to mention the selection of color a palettes and general pixel pushing.
On a similar front having a UX guy on hand is invaluable. If you don’t have one, then read Steve Krug’s Don’t Make Me Think book and then go and find yourself a UX guy :)
The Facebook API is now pretty mature and is very stable (compared to the last time I used it a few years ago).
One annoying things is that the calls that you make to explore the user’s social graph (via the FB.api function) are very low-level and the data is returned in a callback. This can make your code very messy, since you often have to chain your app’s logic in the callback function, if your app needs data before it can proceed. For example:
FB.api('/me', function(response) { // do something with response here // make next api call (with nested callback) here });
Additionally if you need to, for instance, retrieve friend profile data you can land up making hundreds of calls:
// grab all my friends FB.api('/me/friends', function(response) { $.each(response.data, function(index, value) { // get profile for each friend (this triggers another HTTPS call) FB.api('/' + value.id , function(profile) { // do something with profile data }); }); });
Luckily there is a better way, the FB.query and FB.waitFor functions. These let you run queries against a user’s data using a SQL like language (FQL) and then block waiting for the response. This greatly reduces the time it takes to retrieve data and keeps your apps logic a bit simpler:
var query = FB.Data.query('SELECT uid,name,current_location FROM user WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me())'); query.wait(function(rows) { $.each(rows, function(index, value) { // do something with friend data here }); }); // rest of app logic continues here
Firebug
Get it (hopefully this isn’t news to you). This tool is invaluable for web development. It allows you to debug JavaScript, view the DOM, check network traffic and tweak CSS in browser.
It’s also a good idea to test you app in different browsers as you go. During this app’s build I had FireFox, Chrome and IE 8 all running so I could see that everything was working. Luckily by using Twitter Bootstrap and jQuery I had no cross-browser issues at all other then the fact that IE didn’t get nice round corners.
Make sure it validates
The W3C provides a tool for validating web pages. It’s always good to be sure that your pages validate, not just because it is good practice but because it stops browsers from entering their “quirks mode” which often causes odd and quirky behavior.
The Web Developer plugin for Firefox provides a handle shortcut for submitting your local HTML to the W3C validator.
In my case everything validated except for the Facebook namespace declaration and the custom Facebook tags such as fb:like. Whilst there are ways around this I decided on not been too purist.
HTML5
Try use HTML5 for your app. It works very nicely. For IE browsers (before 9.0) use the HTML5 shim to get some HTML5 support.
However Twitter Bootstrap does not support IE6, so I used this trick to make any IE6 browsers redirect the user to a “browser unsupported” page with tips on how to upgrade their browser. In the _
_section add the following comment:<!--[if lt IE 7]><script type="text/javascript">window.location = 'notsupported.html';</script><![endif]-->
This uses the conditional comments feature of Internet Explorer to redirect IE6 browsers to the notsupported.html page, other browsers will simply treat it as a HTML comment.
Improving load times
It is best practice to load all the CSS files in the section of the page and all the JavaScript files at the end of the page (just before the </body> tag). This makes the page feel much more responsive as the browser can start to render and layout the page before everything is loaded.
Having said that, a complex web app tends to pull in a lot of CSS and JavaScript files and minimizing the number of objects that are fetched from the server is a key factor in having a speedy app.
Ideally you only want your app to load a single CSS file and a single Javascript file. To achieve this in my app I concatenate the CSS files and JavaScript files (in the correct order) and then compress them using Yahoo’s YUI compressor.
Since I wanted this to be a repeatable process I used Ant to create a build script to do this. Firstly the concatenation:
<concat destfile="${tmp.dir}/all.js"> <fileset file="${www.dir}/jquery.min.js" /> <fileset file="${www.dir}/jquery-ui-1.8.16.custom.min.js" /> <fileset file="${www.dir}/jquery.masonry.min.js" /> <fileset file="${www.dir}/bootstrap-modal.js" /> <fileset file="${www.dir}/bootstrap-twipsy.js" /> <fileset file="${www.dir}/bootstrap-alerts.js" /> <fileset file="${www.dir}/app.js" /> </concat>
And then the compression:
<exec executable="java"> <arg value="-jar"/> <arg value="yuicompressor-2.4.7.jar"/> <arg value="${tmp.dir}/all.js"/> <arg value="-o"/> <arg value="${tmp.dir}/all.js"/> </exec>
Now during development you don’t want to be dealing with concatenated and compressed files so my PHP pages contain a snippet like this:
<?php if ($config['mode'] != 'prod') { ?> <script src="jquery.min.js"></script> <script src="jquery-ui-1.8.16.custom.min.js"></script> <script src="jquery.masonry.min.js"></script> <script src="bootstrap-modal.js"></script> <script src="bootstrap-twipsy.js"></script> <script src="bootstrap-alerts.js"></script> <script src="app.js"></script> <?php } else { ?> <script src="all.js?@buildtimestamp@"</script> <?php } ?> </body>
With mode getting set in my app’s config file.
Of course there are a ton of other things you can do to tune your app. Get the YSlow addon for Firebug and run the report on your pages to see what can be done.
Cache busting
Ant can also very helpfully update parts of your files whilst copying them around. This can be used to burn in all sorts of things, like version numbers and build timestamps. It can also be used create a effective cache buster when a new version of your app is released….
Firstly you need to set up an Ant property with an appropriate value:
<tstamp> <format property="buildtimestamp" pattern="yyyyMMddHHmmssSSS" locale="en,UK"/> </tstamp>
Then in your HTML you can append @buildtimestamp@ following to file references:
<link rel="stylesheet" href="all.css?@buildtimestamp@"/> ... <script src="all.js?@buildtimestamp@"></script>
Then lastly apply a filter in Ant when you are copying the files around:
<copy todir="${tmp.dir}"> <fileset dir="${www.dir}"> <include name="**/*.php" /> </fileset> <filterset> <filter token="mode" value="prod" /> <filter token="release" value="${rel}" /> <filter token="buildtimestamp" value="${buildtimestamp}" /> <filter token="builddate" value="${builddate}" /> </filterset> </copy>
PHP config file
To keep things nice and clean, I created a config.php file looking like this:
<?php if ( ! defined('CONFPATH')) exit('No direct script access allowed'); // set this to '@' + 'mode' + '@' during development so that style sheets and javascript files are individually included (see index.php) // ant build scripts will replace this at build time $config['mode'] = '@mode@' ?>
Then in my other PHP files I pulled in the config file using:
<?php define('CONFPATH','config.php'); require_once CONFPATH; ?>
As you can see from the comments and the Ant snippets above, I overwrite the mode value with _‘prod’_ during my Ant build process to ensure that my compressed .js and .css files are used.
Miscellaneous files
Finally make sure you add a favicon and a robots.txt to avoid annoying 404 webserver logs. For fun why not add a humans.txt too.
Summary
Well that pretty much covers it. Hopefully there are one or two new ideas here that you can use in your apps. Feel free to post any questions in the comments section below.