Friday, August 6, 2010

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.

No comments:

Post a Comment