FBJS
From Facebook Developers Wiki
FBJS is Facebook's solution for developers who want to use JavaScript in their Facebook applications. We built FBJS to empower developers with all the functionality they need, and to protect our users' privacy at the same time.
Contents |
How It Works
Most providers who allow developers to embed JavaScript within their domain force developers to use iframes to sandbox their code. Facebook has taken a different approach to this problem. JavaScript that you give us gets parsed, and any identifiers (function and variable names) get prepended with your application ID. For example, the following code block:
| function foo(bar) { var obj = {property: bar}; return obj.property; } |
becomes:
| function a12345_foo(a12345_bar) { var a12345_obj = {property: a12345_bar}; return a12345_obj.property; } |
This creates a virtual scope for every application that runs within Facebook. From there we expose certain functionality through a collection of JavaScript objects that allow you to modify your content on Facebook. Our objects are made to mimic the functionality of JavaScript as closely as possible, but it may take some getting used to for people who are already adept with JavaScript.
The Basics
The JavaScript syntax you've come to know and love (or hate) is exactly the same. You can create objects, use anonymous functions, create timeouts and almost any other thing you can think of. Modifying the DOM tree is slightly different, however.
Take this example FBML code, for instance:
| <a href="#" onclick="hello_world(this); return false;">Hello World!</a> <script> <!-- function random_int(lo, hi) { return Math.floor((Math.random() * (hi - lo)) + lo); } function hello_world(obj) { var r = random_int(0, 255), b = random_int(0, 255), g = random_int(0, 255); var color = r+', '+g+', '+b; obj.setStyle('color', 'rgb('+color+')'); } //--> </script> |
As you can see, creating FBJS is very similar to JavaScript. Note, however, that this example may not work as expected:
| <a href="#" id="hello">Hello World!</a> <script> <!-- function random_int(lo, hi) { return Math.floor((Math.random() * (hi - lo)) + lo); } function hello_world(obj) { var r = random_int(0, 255), b = random_int(0, 255), g = random_int(0, 255); var color = r+', '+g+', '+b; obj.setStyle('color', 'rgb('+color+')'); } hello_world(document.getElementById('hello')); //--> </script> |
In profile boxes, inline scripts are deferred until the first "active" event is triggered by a user. An active event is considered either onfocus, onclick, onmousedown, and so forth. Basically anything that requires a mouse click is an "active" event. On a canvas page, however, this example works just fine.
Also, please note it's very important that you use the syntax in the example above with your <script> tag hugging HTML comments. Otherwise <'s will be stripped out which makes coding very difficult ;). In the future we plan to modify our FBML parser to accept FBJS code without HTML comment wrappers, but for now it's required. -- Seems this is no longer true and you should be fine without them (though it doesn't hurt to include them and they'll be removed by the FBJS parser anyway).
FBJS DOM Objects
Retrieving Objects
A handle to an FBJS DOM object can be retrieved by either calling document.getElementById, or document.createElement. Additionally, the "this" pointer in DOM events also points to the target of the event.
Manipulating Objects
FBJS DOM objects implement most of the same methods regular JavaScript objects implement including: appendChild, insertBefore, removeChild, and cloneNode. Properties like parentNode, nextSibling, src, href (and many many others) have been redefined as a couplet of getters and setters.
Instead of obj.parentNode just call obj.getParentNode(), and so on. Most of the properties are easy to figure out, but here's an exhaustive list of properties in JavaScript and how they translate to FBJS:
| JavaScript | FBJS getter | FBJS setter | Description | |
|---|---|---|---|---|
| parentNode | getParentNode | |||
| nextSibling | getNextSibling | |||
| previousSibling | getPreviousSibling | |||
| firstChild | getFirstChild | |||
| lastChild | getLastChild | |||
| childNodes | getChildNodes | Returns a snapshot array of childNodes | ||
| innerHTML | n/a | setInnerFBML | Note that this can throw an error if you pass a string directly. Use Fb:js-string to create the string first then pass that variable. | |
| innerHTML | n/a | setInnerXHTML | Beta feature. Allows you to set the innerHTML of an element by passing in a string of XHTML. The XHTML is sanitized according to FBML rules and then placed into the document. | |
| innerText/textContent | n/a | setTextValue | Not exactly like setInnerFBML as this will only allow text (no HTML)! It will remove all childNodes of the element it is called on. | |
| form | getForm | Doesn't work, use document.getElementById('formid') instead | ||
| action | getAction | setAction | ||
| value | getValue | setValue | ||
| href | getHref | setHref | ||
| target | getTarget | setTarget | ||
| src | getSrc | setSrc | ||
| className | getClassName | setClassName | ||
| tagName | getTagName | |||
| id | getId | setId | ||
| dir | getDir | setDir | ||
| checked | getChecked | setChecked | ||
| clientWidth | getClientWidth | |||
| clientHeight | getClientHeight | |||
| offsetWidth | getOffsetWidth | |||
| offsetHeight | getOffsetHeight | |||
| n\a | getAbsoluteTop | Returns the elements absolute position relative to the top of the page. Useful because of lack of offsetParent support. | ||
| n\a | getAbsoluteLeft | Same as getAbsoluteTop, but horizontally. | ||
| scrollTop | getScrollTop | setScrollTop | ||
| scrollLeft | getScrollLeft | setScrollLeft | ||
| scrollHeight | getScrollHeight | |||
| scrollWidth | getScrollWidth | |||
| tabIndex | getTabIndex | setTabIndex | ||
| title | getTitle | setTitle | ||
| name | getName | setName | ||
| cols | getCols | setCols | ||
| rows | getRows | setRows | ||
| accessKey | getAccessKey | setAccessKey | ||
| disabled | getDisabled | setDisabled | ||
| readOnly | getReadOnly | setReadOnly | ||
| type | getType | setType | ||
| selectedIndex | getSelectedIndex | setSelectedIndex | ||
| selected | getSelected | setSelected | ||
| location | n/a | setLocation | ||
| style | getStyle | setStyle | ||
| n/a | getRootElement | used as document.getRootElement - returns the top-level element of your profile box or canvas page |
Manipulating Styles
Styles are set with the setStyle method and queried with the getStyle method. setStyle can set multiple styles using the syntax:
| obj.setStyle({color: 'black', background: 'white'}); |
Or one style at a time using:
| obj.setStyle('color', 'black'); |
Beware you need to camelize style names. This works:
| obj.setStyle('textDecoration', 'underline') |
But this won't:
| obj.setStyle('text-decoration', 'underline') |
You must also remember to use 'px' notation when referring to positions or height/width, and so forth.
This works:
| obj.setStyle('width', '340px') |
But this doesn't:
| obj.setStyle('width', '340') |
This is important to remember when you're using algorithms to calculate those values. You can't just use the calculated variable x like:
setStyle('left', x), but rather like setStyle('left', x+'px').
Additional functionality for manipulating CSS classes has been added to FBJS DOM nodes.
- addClassName(className)
- Adds a class name to the
classNamestring if it isn't already present. - removeClassName(className)
- Removes a class name from the
classNamestring if it present. - toggleClassName(className)
- If a class name exists, it removes it. If it doesn't exist it adds it.
- hasClassName(className)
- Returns true if the class name exists or false otherwise.
Setting Content
innerHTML isn't implemented for security reasons. Three alternatives exist.
- obj.setTextValue(newText) can be used to set a literal text value inside of your DOM object (no HTML or FBML accepted).
- obj.setInnerFBML(fbJsStringVar) can be used to put HTML or FBML inside of your DOM object. Note that you need to create a Fb:js-string object first and pass it in as passing a string literal will result in an error.
- obj.setInnerXHTML(string) is a beta feature that allows you to place a string of XHTML directly into the document. The XHTML is sanitized in JavaScript before being rendered.
Working with Text Fields
Textbox selections have been implemented with the methods getSelection and setSelection. getSelection returns an object with properties start and end which correspond to the W3C-style attributes selectionStart and selectionEnd. setSelection takes two arguments, start and end (optional). This abstraction was added because Internet Explorer does not support selectionStart and selectionEnd. Since it is quicker in IE to retrieve both values together, they were coupled together into a single getter and setter. This function should work the same in all browsers with no extra work from you.
Creating FBML Elements
You can also use createElement to create FBML elements, although this is currently limited to fb:swf. Once it's created, it works just like any other DOM object does, however, once it is attached to the DOM you cannot move it and obj.getElementsByTagName('fb:swf') does not work.
| var newSwf = document.createElement('fb:swf'); |
Events
Events can be added to FBJS DOM objects using the W3C-style addEventListener method. The third parameter, useCapture, is not supported. removeEventListener is also supported. In addition to the W3C event methods, we've also added listEventListeners and purgeEventListeners.
- listEventListeners(eventName)
- Returns an array of handles of all events that have been added to this event. Events that were added in FBML using the on<event> attributes will also be included
- purgeEventListeners(eventName)
- Removes all event listeners for a given event. This also removes events that were added as attributes in FBML.
Event handlers are called with one parameter, which is an object with information about the event. In the case of event handlers added as attributes, this object will be accessible through the "event" variable (just as it is in regular JavaScript). The event will have attributes target, type, pageX, pageY, ctrlKey, keyCode, metaKey, and shiftKey. It also implements two methods:
- stopPropagation
- Prevents this event from propagating to any more elements further up in the DOM.
- preventDefault
- Cancels the default behavior of this event without stopping propagation. For instance, preventDefault on an onfocus event will prevent that element from getting focus.
- getId on event object of a listener function
When using the getId() method inside an event listener function on the event object, the following syntax may be used to retrieve the ID of the object that fired the event:
| <div id="firedByDescription"></div> <div id="foo"></div> <div id="bar"></div> <script> //disclaimer: sample code block meant only to demonstrate functionality function myEventHandler(evt) { //we'll use this div later to drop stuff into it firedByDescription = document.getElementById('firedByDescription'); if (evt.type == 'mouseout') { //if the event is a mouseout, empty out the description div, and exit the event listener firedByDescription.setTextValue(''); return true; } //otherwise... do some processing: //*VERY IMPORTANT*: note that the object, which fired the event is located two nodes up in the DOM tree //See note below //eventFiredBy_ObjectId = evt.target.getParentNode().getParentNode().getId(); //On newer versions, it seems that there is no need to go up two levels int he DOM tree, hence eventFiredBy_ObjectId = evt.target.getId(); //works, whereas the first does not! //**NOTE** My testing of this suggests that when you call addEventListener() it adds it to the element, AND all it's descendants // This can then cause the event to be fired multiple times, as it is fired for the element and it's descendant elements. // When fired by a descendant element, you will probably have to do some kind of getParent()-ing // I'm raising this as a bug, as it does make things a little unworkable! //once you have the ID, you may, for example, drop its id into the firedByDescription div: firedByDescription.setTextValue(eventFiredBy_ObjectId); //... or do some conditional processing: if (eventFiredBy_ObjectId == 'foo') { //do something if the event was fired by 'foo' } else { //do something if the event was fired by 'bar' } } //add event listener to 'foo' div (mouseover & mouseout) document.getElementById('foo').addEventListener('mouseover',myEventHandler); document.getElementById('foo').addEventListener('mouseout',myEventHandler); //add *the same* event listener to 'bar' div (mouseover & mouseout) document.getElementById('bar').addEventListener('mouseover',myEventHandler); document.getElementById('bar').addEventListener('mouseout',myEventHandler); </script> |
This functionality is very useful in cases where you have one event handler for multiple objects of the same type. Take for instance a shopping cart of some sort, or any type of object browser. When a user moves her mouse over one of the items in the cart, you may want to conditionally display information about the item -- by finding its ID, you may associate a description text for that item and display it to the user in another div. As you can see, using event listeners can be a very powerful way to display useful additional information to the user, based on where they move their mouse within your Facebook application. Happy coding and creativity!
AJAX
FBJS supplies a very powerful AJAX object for developers. Facebook proxies all AJAX requests and optionally runs useful post-processing on the data returned, such as JSON, or FBML parsing. To use it, just instantiate a new AJAX class. It supports the following properties:
- ondone(data)
- An event handler which fires when an AJAX call returns. Depending on .responseType, data is an object, a raw string, or an FBML string.
- onerror
- An event handler that fires when an error occurs during an AJAX call.
- requireLogin
- If you set this to true the AJAX call will require the user to be logged into your application before the AJAX call will go through. The AJAX call will then be made with the regular fb_sig parameters containing the user's identity. If they refuse to login, the AJAX call will fail.
- responseType
- This can be one of Ajax.RAW, Ajax.JSON, or Ajax.FBML.
- useLocalProxy
- Beta. If this is true and you are using RAW or JSON type, the Ajax object will attempt to use fb:local-proxy to make a direct call to your app server. See FBJS_LocalProxy for more details.
- Ajax.RAW
- The response from your server is returned to your callback in its original form.
- Ajax.JSON
- The response from your server is parsed as a JSON object and returned to your callback in the form of an object. Properties of your JSON object that are prefixed with "fbml_" are parsed as individual FBML strings and returned as FBML blocks. These blocks can be used on a DOM object with the setInnerFBML method. Each variable and its value in the response is limited to a combined length of 5000 characters. Note: be sure to use json_encode or else you may see odd results with large data sets. See Bugzilla #363 for more information. json_encode is available by default in most PHP5 installations, and implementations for many other languages are available at json.org.
- Ajax.FBML
- The response from your server is parsed as FBML and returned as an FBML block. This block can used on a DOM object with the setInnerFBML method.
And two methods:
- post(url, query)
- Start an AJAX post.
urlmust be a remote address, andquerycan be either a string or an object that is automatically converted to a string. - abort()
- Aborts an AJAX post.
Here's an example showing most of the functionality of AJAX:
Ajax Example
Dialogs
Dialog is an object we've created to allow you to hook into our base dialog abstractions. It allows you to create rich and fully dynamic dialogs for your application.
- Dialog(type)
- (constructor) type can be either Dialog.DIALOG_POP or Dialog.DIALOG_CONTEXTUAL.
- Dialog.DIALOG_POP
- This is the type of dialog that shows up when you delete a wall post.
- Dialog.DIALOG_CONTEXTUAL
- This is type of dialog that shows up when you delete a minifeed story.
- onconfirm
- An event handler that fires when the user selects the button designed as "confirm" (left most button). If this event doesn't return false the dialog will be hidden.
- oncancel
- An event handler that fires when the user selects the button designed as "cancel" (right most button). If this event doesn't return false the dialog will be hidden.
- setStyle
- Allows you to set the style of the parent dialog node
- showMessage(title, content, button_confirm = 'Okay')
- Displays a dialog with only a confirm button.
titleandcontentcan be either strings or pre-rendered FBML blocks. - showChoice(title, content, button_confirm = 'Okay', button_cancel = 'Cancel')
- Displays a dialog with Confirm and Cancel buttons.
titleandcontentcan be either strings or pre-rendered FBML blocks. - setContext
- (only applicable for DIALOG_CONTEXTUAL). Sets the context of a dialog, which basically means where the cursor arrow is pointing.
- hide
- Hides this dialog if it is visible.
FBML Blocks
Blocks of pre-rendered FBML can be exported into your JavaScript scope on page load. To do this, simply wrap a block of FBML inside an <fb:js-string var="variable_name"> tag (see Fb:js-string for more information). Instead of rendering the block of FBML on the page it is put into a FBML block variable, which you can then use in your JavaScript with setInnerFBML. This is useful, because tags like <fb:swf> get rendered without waitforclick restrictions. FBML blocks can also be retrieved from AJAX calls, as explained above.
Animation
Facebook has provided a powerful animation library in FBJS. See Animation for more details.
Examples
- Hello World!
- Typeahead widget
- Ajax Typeahead widget
- Dialogs
- Dynamic Dialogs using Ajax
- Ajax
- Dynamic Tables
- Slider Widget
- Tabs
Tips
- Don't create JavaScript which depends on a sensitive DOM structure. Code like
this.getElementByTagName('div')[1].getFirstChild().getLastChild().setStyle('color', 'white')is very fragile and may randomly break if we change the way certain elements are rendered.
- Most FBJS DOM methods are chainable. For instance, instead of:
-
var obj = document.createElement('div'); obj.addEventListener('click', click); obj.addEventListener('mousemove', mousemove); obj.setStyle('color', 'black'); - You can do:
-
document.createElement('div').addEventListener('click', on_click).addEventListener('mousemove', mousemove).setStyle('color', 'black');
- You aren't allowed to extend base objects like Function or Array, however we do provide a typical "bind" implementation on the Function prototype.
- FBJS objects don't contain handles to any of their actual DOM objects, however if you use Firebug, the console can show you exactly to what an object is referring. Try console.dir on an FBJS DOM object. In your console you'll see a
PRIV_objattribute which is the actual DOM node represented by your FBJS DOM handle. This can help you figure out what FBJS is doing behind the curtains. This trick also works with all other FBJS objects such as AJAX and FBML blocks.
- If you want to use timed fading/unfading of elements, creating 'panes' with drop-shadows or generic dragging, check out the small FBJS effects library Backface, at http://supercodex.com/backface/backface.zip, with a demo at http://apps.facebook.com/backface.
- Use Firebug to troubleshoot and diagnose anything that isn't working with your FBJS.
- Consider using the Include files support to save processing/load times and bandwidth
