Saturday, August 7, 2010

Example

This is an example of how you can use this extension in HTML5 (assume all code is in the body element):

<h3>Members</h3>
<embed data-fb="login-button" data-show-faces="true" />
<h3>Recent activity</h3>
<embed data-fb="activity" data-site="***" data-width="200" data-header="false"
data-border_color="#fff" data-recommendations="false" />
<div id="fb-root"></div>
<!-- the JavaScript API -->
<script src="http://connect.facebook.net/en_US/all.js"></script>
<!-- the extention script from this article -->
<script src="/scripts/fb.js"></script>
<script>
//<![CDATA[
FB.init({apiKey: '***', appId: '***', status: true, cookie: true, fbml5: true});
//]]>
</script>


This would be the equivalent XHTML code:

<h3>Members</h3>
<fb:login-button show-faces="true" />
<h3>Recent activity</h3>
<fb:activity site="***" width="200" header="false"
border_color="#fff" recommendations="false" />
<div id="fb-root"></div>
<!-- the JavaScript API -->
<script src="http://connect.facebook.net/en_US/all.js"></script>
<!-- the extention script from this article -->
<script src="/scripts/fb.js"></script>
<script>
//<![CDATA[
FB.init({apiKey: '***', appId: '***', status: true, cookie: true, fbml5: true});
//]]>
</script>

Friday, August 6, 2010

Limitations and possible improvements

At the moment this solution is all I need, but this doesn't mean that it is complete. For instance, nested FBML is not possible via embed tags. A possible solution for this is using nestable HTML5 elements such as divisions (div).

Also, when inheriting the attributes from the embed element to the wrapper element I didn't check whether or not the attributes are valid in div's. Common attributes such as id, class and title will not cause any troubles, but for instance type will. The check could be done, but for me this is overhead since I'm aware of what attributes are valid when developing.

For the rest this solution should be quite flexible. Since I didn't hardcode any changes in the original connect script it keeps allowing updates from Facebook's JavaScript API.

I hope this can help other people who are embedding Facebook plug-ins in their websites. Any comments, remarks, improvements, ... are welcome.

Another thing I need to add is that I've only tested this code on Mozilla Firefox 3.6.x (FreeBSD 8.0 and Windows Vista) and IE8 (Windows Vista). Feel free to comment when your attempt has succeeded (or failed) on a specific browsers. Please include the browser's version and OS.

The code

This ECMA script has to be executed on the client-side between the facebook connect script and the FB.init call.

The JSON object given with the FB.init call now also uses the fbml5 property to enable parsing via the FBML5 module. This boolean property works the same as xfbml, but then for fbml5.

/*
* FBML5 extension for Facebook's JavaScript API
* by Dennis Degryse (dennisdegryse@gmail.com)
*/

/* HTML5 support via <embed> tags and userdata attributes */
FB.provide('FBML5', {
parse : function(c, a) {
c = c || document.body;

var b = 1, d = function() {
b--;

if (b === 0) {
a && a();
FB.Event.fire('xfbml.render');
}
};

FB.Array.forEach(FB.XFBML._tagInfos, function(f) {
if (!f.xmlns)
f.xmlns = 'fb';

var g = FB.FBML5._getDomElements(c, f.xmlns, f.localName);

for ( var e = 0; e < g.length; e++) {
var h = g[e];
b++;
FB.XFBML._processElement(h, f, d);
}
});

window.setTimeout( function() {
if (b > 0)
FB.log(b + ' FBML5 tags failed to render in '
+ FB.XFBML._renderTimeout + 'ms.');
}, FB.XFBML._renderTimeout);

d();
},
_getDomElements : function(a, e, d) {

var c = 'data-' + e, b = [], g = a.getElementsByTagName('embed');

for (var h = 0; h < g.length; h++)
if (g[h].hasAttribute(c) && g[h].getAttribute(c) == d)
b.push(FB.FBML5._transform(e, g[h]));

return b;
},
_transform : function (e, g) {
var a = document.createElement('div');
var c = g.parentNode;

for (var b = 0; b < g.attributes.length; b++)
a.setAttribute(g.attributes[b].nodeName, g.attributes[b].nodeValue);

c.insertBefore(a, g);
c.removeChild(g);

return a;
}
});

/* overriding the init function to enable the FB.FBML5 module */
FB_init_original = FB.init;
FB.init = function(a) {
FB_init_original(a);

if (a.fbml5)
window.setTimeout(function(){
if(FB.FBML5)
FB.Dom.ready(FB.FBML5.parse);
}, 0);
};

/* overriding the getAttribute function to enable the HTML5 user-data attributes
on divs */
var HTMLDivElement_getAttribute_original = HTMLDivElement.prototype.getAttribute;
HTMLDivElement.prototype.getAttribute = function (x)
{
if (this.hasAttribute(x))
return HTMLDivElement_getAttribute_original.call(this, x);
else
return HTMLDivElement_getAttribute_original.call(this, 'data-' + x);
};

A solution: Extending the JavaScipt API

Yes, I am too modest to call this "THE solution" and theoretically spoken this problem has many solutions. I like my solution because it is pretty clean and follows common sense. It also doesn't require major changes to eventually existing code.

First of all we need to get rid of the namespace declaration and thus the FBML elements. There is a solution for that, which is also explained in the API docs. This solution rests on the fact that all FBML tags are eventually parsed as iframes, so one could immediately insert iframes instead of the FBML tags. However, this solution takes away the dynamics and ease of use from the FBML. FBML removes the task from the developer to encode some data (for instance in the src attribute) and to bind events to the generated elements. These things are all done by the XFBML parser in the JavaScript API.

Instead of inserting iframes directly, I was thinking some kind of placeholder element which is in the HTML5 specification and can contain all necessary attributes (like the FBML tags). Then the only thing to do is to map these elements to iframes, using the existing JavaScript API.

Finding such an element is not that hard. In fact it is very trivial: the embed-element. As we are embedding Facebook plug-ins it sounds very natural if we did that via embed elements. The next requirement (supporting all attributes) is also very quickly met when using HTML5. HTML5 allows elements to have so called user-data. This user-data is represented in attributes which have the following structure:
data-somename="somevalue"

where somename is the name of the user-data field and somevalue the associated data. Once we know this, we can make up some rules. Lets say we have the following abstract FBML tag:
<fb:name att1="val1" att2="val2" ... attn="valn"></fb:name>

In order to have a good map we need to keep all attributes and also know what FBML element we are talking about. I first thought of storing the name of the FBML element in the type attribute of the embed element, but this causes some browsers to search for additional plug-ins which are associated with the given type. This is annoying, so let's just store the name in a user-data attribute. The rules here are the following:
- store the name of the original FBML element in the data-fb attribute (change 'fb' to another namespace name if necessary)
- store attributes in a data-attname attribute, with attname being the name of the attribute and the value following as the value of the user-data attribute.

This brings us to the following result (using the abstract FBML tag):
<embed data-fb="name" data-att1="val1" data-att2="val2" ... data-attn="valn" />


This will make the markup of the page valid (except when you got other errors of course), but it won't render any useful content just yet. For that we need to extend the JavaScript API so that it knows that specific embed elements need to be parsed too. One could rewrite the API so that the embed tags are parsed directly to the resulting content, but to reduce the lines of code and use as much of the available API as possible I've just added a tiny module that pre-parses the embed elements to div elements (because embed elements cannot contain child elements). Then these divs elements are parsed further by the existing API functionality. After that, the divs and their generated XHTML content replace the embed element.

Note that the div will inherit all attributes from the embed tag. This is necessary because the classes that do the parsing of each specific element still need to read them. It is also useful for the developer that attributes like the id, class and title are inherited so that CSS styles or other connected functionality keeps working on the element.

Because the parsing of XFBML is done when executing the FB.init function I had to override this function to make it also parse our custom (lets call them FBML5) elements.

I've also overridden the built-in HTMLDivElement.getAttribute method. This was necessary to allow the divs to be processed further by Facebook's JavaScript API. The element parsers read attributes from the FBML tags, so in order to let them be read the right attributes from the div I simply stated that when an attribute 'x' is not found, it has to return the value of attribute 'data-x'.

This last modification could cause problems with existing scripts, so be aware of this when you use user-data attributes which have the same name as normal attributes.

The problem with HTML5

This XFBML might be extremely awesome for XHTML languages, but in my case it's not directly compatible with my website because I use HTML5. The thing is that in order to use FBML, an XML namespace must be declared. This is mostly done in the html element of the page and looks like this:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/200
8/fbml">...</html>

Now, declaring an XML namespace is not yet confirmed to be valid HTML5. Using the (experimental) validator on W3C will mark these declarations (and all FBML tags) as erroneous code. This means that until now it is morally illegal to use FBML in HTML5.

I've read that HTML5 aims to be compatible with HTML4 and XHTML1.0 Strict and will just mark initially unsupported elements or attributes as deprecated. I don't know whether this can be confirmed. Even if so, this still this means that our xmlns attribute could be deprecated (in HTML5) instead of being completely supported. I don't like to use deprecated code, so this still isn't a good solution to me.

Also note that FBML will be parsed by many browsers, even if the page is served as HTML5. So developers who don't care about valid markup can just use it.

The Facebook Markup Language

A couple of weeks ago I decided to update my website and use HTML5 markup for it. I have no specific reason for this choice except from my curiosity which wants to find out what HTML5 is (or will be) capable of.

Another value that I wanted to add to my homepage is semantics. The 'Semantic Web' has been described as an important component of Web 3.0. Web 3.0 is also many other things (it even has different definitions for other people). The important thing about semantics is that it tries to link content over the internet in a huge graph. Many attempts have been done to do this efficiently (for instance RDF), but they all are different from each other in many ways. This is why it was hard for me to decide what method was best for my site.

Then I remembered that Facebook consumes a semantic protocol named Open Graph. Facebook is quite popular and innovative. I use Facebook myself, as do many people that I know. Therefore I dug deeper into the Facebook API to find out what I could use to make my website better, regarding semantics.

It didn't take long to learn how Facebook can be integrated in other websites. I won't describe this completely because everything is very straight forward and the API has excellent docs which can be found on the Facebook Developer website.

As one can see Facebook uses its own markup language (X)FBML which can be embedded in XHTML (because it is an XML subset). The FBML tags are then transformed to XHTML tags (mostly iframes) via a client-side ECMA script (aka javascript) so they become useful to modern browsers.