Making a Facebook app (with Django) - part 2: JavaScript and FBJS
August 17th, 2008 by Eddie SullivanWelcome to the second part in my series of posts about creating a Facebook application. I am using Django as my web development framework, but this post doesn't have much to do with Django, since it deals with the front end. In particular, it talks about how to write JavaScript that can work both in and out of Facebook.
As I mentioned last time, Facebook lets developers use a subset of JavaScript, which they call FBJS. The FBJS is transformed on the fly into JavaScript as the page is loaded. All variables and functions you define or reference are prepended with a string like "a123456789_", including calls to document.getElementById and setTimer and the like. This is done in order to restrict what you can do with DOM elements, to avoid cross-site-scripting attacks and unwanted user-hostile behavior. FBJS is fairly well documented, so if you plan to do some Facebook JavaScript development, you should start there.
The biggest restriction that FBJS imposes is that you can no longer access the attributes of DOM elements directly, but must go through an abstraction API consisting of a series of setters and getters. For example, instead of saying something like imageEl.src = myImageUrl, you instead need to call imageEl.setSrc(myImageUrl).
This is not too big of an adjustment, if you are accustomed to writing raw JavaScript. It actually has some advantages, because FBJS abstracts away some of browser-dependent aspects into a standard API.
The challenge, however, becomes apparent when you try debugging your JavaScript. With Internet Explorer and its horrendously useless error messages and debugging facilities, it's pretty much hopeless, since the line numbers don't correspond to line numbers in your source code. However, even with Firefox and the wonderful Firebug debugger, all the name mangling and rewriting makes tracking down problems more difficult. Ideally you would want to debug as much as possible outside of Facebook, before then testing on the real site.
To make this easier, I developed some useful code to simulate as much as possible the FBJS environment when running locally or outside of Facebook. The idea was to re-create all the accessor functions on all DOM objects. You can download the file here. You're free to use it, but please don't link directly to the file on my site - copy it to your site and use it there. And read the description below first.
I considered several different approaches before settling on the final version. I considered trade-offs of performance and possible side-effects. Rejected approach number one was to modify Object.prototype to contain the new getters and setters. This would have been the fastest, but can cause problems with code like the following:
for (var param in obj) { // Do something with obj[param] }
This would end up enumerating all of the added functions as well as the properties of the object in question. This is why it is generally considered a bad idea to modify Object.prototype.
The second rejected approach was to create a wrapper class with all the accessor functions, that performs the requisite getting and setting on a member variable containing the actual DOM object. This one could have worked well, but it would have been a lot of code to implement, and would have made debugging with Firebug more difficult because of the added layer of indirection.
The approach I settled on was to simply provide wrapper functions for document.getElementById and document.createElement, and for each element that is "getted" or created, to copy in the required member functions. This has a perfomance penalty during the getting or creation of elements, but the calls themselves are speedy. There is likely to be a lot more manipulation than getting, I would think.
How to use this code
To use this code, just reference this file from your web page before any of your own JavaScript, but only when you are outside of Facebook. Make sure to call fbGetEl and fbCreateEl instead of document.getElementById and document.createElement. In the version of your page that is intended to be run inside of Facebook, include this code:
function fbGetEl(elId) { return document.getElementById(elId); } function fbCreateEl(elType) { return document.createElement(elType); }
Alternatively, if you are feeling a little tricky, you can add in some code like this to the end of fbHelper.js
document.getElementById = fbGetEl; document.createElement = fbGetEl;
Then you can continue to use document.getElementById and document.createElement. (Note that I have not tested this approach.)
I hope this comes in handy for some people.
More FBJS tips
Beware of the cache!
If you make a change to your .js file and don't notice any difference in your app's behavior, chances are you are getting bitten by the cache bug. To get around this, Facebook recommends adding "cache-breaker" code to the URL. For example, instead of linking to "myFile.js", link to "myFile.js?v=2". The extra text from the question mark on is usually ignored by web servers for static files. In my Django context processor, I have code like the following:
from svnVersion import svnVersion # Add this function to your TEMPLATE_CONTEXT_PROCESSORS # list in settings.py def fbContextProcessor(request): return { # Add any other template vars you want here. 'cacheBreaker':'?v=%s' % svnVersion }
Then in my template, whenever a have a link, I do something like:
<a href="http://example.com/myFile.js{{ cacheBreaker }}">Click here!</a>
Then every time I update my code, I run a shell script that does
something like:
#!/bin/sh
echo "svnVersion = '`svnversion -n .`'" > svnVersion.py
That way, every time I update my code, I make sure to break the Facebook cache. Alternatively, you could call svnversion straight from Python.
console.log is your friend!
This is true in general when JavaScript programming, but especially so with Facebook. Take the time to put in some extra logging messages while you're developing, and testing and debugging will be much easier. And read up on Firebug as much as possible.
Check out the Animation library.
It's pretty cool, and it works outside of Facebook, too.
Beware the limitations.
Facebook only allows up to five external script files, so keep that in mind. You may unfortunately have to combine two or more .js files into one to work around this restriction. Also, FBJS doesn't run on profile pages until the user has interacted with the app.
September 29th, 2008 at 1:23 pm [...] Making a Facebook app (with Django) - part 3: Python & FBML Chicken Wing Software Resume Get in touch Chicken Scratches Page style (CSS): artistic elegant modern (Javascript required) « Making a Facebook app (with Django) - part 2: JavaScript and FBJS [...]