Tuesday, July 28, 2009

Facebook Ajax and Rendering FBML

July 23 2009
using .NET 3.5, VS 2008, Facebook Toolkit 2.0

{much of the code in these articles has extraneous SPACES inserted so that it can be displayed, and not interpreted by the editor. So beware, you will still have some work to do if you copy all this code...)

In this article we will look at how to render a .NET page into an FBML block that can be passed to any of the Facebook functions that require it. For your “normal” canvas pages it is sufficient to have your web application simply return a page that may or may not include some FBML tags, and Facebook presents that to your user. But for some interactions, your application must return a block of FBML markup text that represents the content you want displayed: for example, in the In-Line publisher interface, your application must supply the content as part of a larger JSON-formatted set of parameters to the FB API method publisher_getInterface. Facebook’s implementation of AJAX also uses FBML markup text as one of its allowable return types, and in conjunction with their javascript method setInnerFBML you can dynamically update the content of any DOM element. The trick is, getting your server-side Ajax handler to return FBML markup text.

“FBML Markup Text” might sound intimidating but FBML is really just HTML with some Facebook-specific tags added. (If you are attempting to write an Ajax-based pager solution for a Facebook Canvas application and you DIDN’T already know that, perhaps you should back up a step…) But this doesn’t really help answer the question “how do I get rendered FBML out of my ASPX page?” Luckily the Facebook Development Toolkit provides some functions that make this pretty easy. The FDT wraps up the .NET method HttpContext.Current.Server.Execute which renders the output from a page or web user control.
Our goal for this series is to create an AJAX-based paged data display control. So far we have created a data access layer that can return a page of data as requested by page number, and a web user control that provides pager navigation. So now, let’s look at Facebook and AJAX: http://wiki.developers.facebook.com/index.php/FBJS#AJAX and http://wiki.developers.facebook.com/index.php/FBJS/Examples/Ajax

Got all that? OK. Check this out, this is the lifecycle:


1. User clicks a page navigation control inside the AJAX-ified DIV
The content of the DIV includes the pager control from the previous article.

2. The javascript on-click function makes an AJAX call to the FB server passing my handler URL
The URL and an array containing any parameters the server side handler need are passed to the Facebook AJAX service.

3. My handler generates new FBML content for the AJAX-ified DIV, responds to FB server
The entire content of the DIV is defined by a Web User Control, which is rendered by an FDT utility.

4. FB servers parse the JSON block, and return the FBML markup to user’s browser by invoking the ondone function
The ondone function is defined inside the click event function.

5. The ondone function updates the content of the AJAX-ified DIV by using the FB javascript method “setInnerFBML”
Facebook won’t allow setInnerHTML, luckily our return data is FBML!

It’s a fairly simple cycle, when you break it down. So let’s look at the page. The Ajax-ified DIV is held on the main page, in this example it is AjaxTest.aspx.

(Complete contents of AjaxTest.aspx)

<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="AjaxTest.aspx.cs" Inherits="AjaxTest" %>
<%@ MasterType VirtualPath="~/MasterPage.Master" %>

< asp: content id="Content1" contentplaceholderid="cpBody" runat="Server">
< script type="text/javascript">
function _ajax_ThingGrid_GoToPage(pageNum) {
var ajax = new Ajax();
ajax.responseType = Ajax.FBML;
ajax.ondone = function(data) {
document.getElementById('thingContainer').setInnerFBML(data);
}
ajax.onerror = function() {
console.log("in onerror");
console.log(error);
}
var params={'pageNum':pageNum};
ajax.post('http://myurl/AjaxTestHandler.ashx’,params);
}
< /script>
< asp : label id="imbedJS" runat="server" type="hidden" text="">
< d iv id="thingContainer">< /div>

< /asp:Content >

(Complete contents of AjaxTest.aspx.cs)


using System;
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
this.imbedJS.Text = @"< script type="text / javascript "> _ajax_ThingGrid_GoToPage(1);< /script >";
}
else
{
this.imbedJS.Text = "";
}
}
}

But where is the data control? It’s not on this page; it’s in a web user control. But where is that WUC? It’s not here either. Nope, it is not here, because the content of the DIV (called “thingContainer” in this example) is generated at run-time by the generic handler module AjaxTestHandler.ashx. On Page_Load a call to the click event function is embedded in the output, causing the DIV to be initialized with page 1 of the data.
Let’s look in detail at the javascript function. You may have noticed it is not referenced anywhere in this page, and you should be guessing that it is referenced in the WUC… and it is, sortof (more on that later…) First, the function creates a Facebook Ajax object, and sets our return type to FBML. This tells the FB Ajax library what to expect from our server-side handler. Next, we define our “ondone” function. The ondone function we use is very simple, but it can really do anything you need it to do. In our example, we simply insert the returned FBML markup into the DIV.

Next we define an “onerror” function. I am simply using the Firebug console here to log an error, however in a production application you would implement a strategy to deal with errors more gracefully.

Finally, we prepare and execute the AJAX postback. We can pass multiple parameters by creating an array of key/value pairs, although here I am simply passing the new page number. The URL of the handler module is the first parameter to Facebook ajax.post function. The handler module uses the FDT FBML renderer utility and our web user controls to create the markup that goes in the DIV.

The Web User Controls
For this example I am using a WUC that holds a grid view, and it also holds an instance of the page navigation WUC from the previous article. I have kept the grid view very simple, allowing it to use default columns and styles. The data is bound server side at run-time, so the code is quite simple:

(Complete contents of wucAjaxGrid.ascx)

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="wucAjaxGrid.ascx.cs" Inherits="wucAjaxGrid" %>
<%@ Register Src="~/wucAjaxTest_PagerControl.ascx" TagPrefix="ajxPager" TagName="ajxPager" %>
< form id="Form1" runat="server">
< d iv >
< asp: g rid view id="ThingGrid" runat="server">
< /asp:GridView>
< /div>
< /form>
< ajxPager : ajxPager id="pager" runat="server" currentpage="1" numitems="">" NumItemsPerPage="10" AjaxPageRequestFunctionName="_ajax_ThingGrid_GoToPage" / >


(Complete contents of wucAjaxGrid.ascx.cs)

using System;
using System.Collections.Generic;
using facebook.web;

public partial class wucAjaxGrid : System.Web.UI.UserControl, IRenderableFBML < dtoPagedGrid >
{
protected List< Thing > things;
protected long TotalItems = 0;

protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack) TotalItems = getTotalPages();
}

public void PopulateData(dtoPagedGrid dto)
{
pager.CurrentPage = dto.CurrentPage;
things = new List< Thing >(dto.Things.Values);
ThingGrid.DataSource = things;
ThingGrid.DataBind();
}

private long getTotalPages()
{
// implement code here to read total number of things from your database
// omitted here for brevity
return 500;
}
}



Client-side we register the paging WUC, we have the simplest grid possible, and we have an instance of the paging WUC. Note that we supply the name of the javascript function defined in the main page (AjaxTest.aspx) as a property of the paging control. This does limit the re-usability of this grid WUC, because it is bound to the parent page by this string.

The server side code has a few more points of interest. We need to reference the library facebook.web, so that our page can implement the interface IRenderableFBML<>. To implement this interface you must have a PopulateData(<>) method. Do you know about .NET generics? If not, here is a good start. In our implementation we are referencing a class that I defined, dtoPagedGrid.

The class dtoPagedGrid is a simple Data Transfer Object class. The FB Renderer utility supports passing one object as the page’s data, but I needed to pass not only the page of data to be displayed, but also the current page number. So, I created a DTO class that holds both pieces of data in one “bundle”.

(entire code for dtoPagedGrid.cs)

using System;
using System.Collections.Generic;

public class dtoPagedGrid
{
// getters and setters omitted for public properties

private Dictionary< Thing > _things;
public Dictionary< Thing > Things...

private long _currentPage;
public long CurrentPage...

public dtoPagedGrid(Dictionary< Thing > _t, long _c)
{
Things = _t;
CurrentPage = _c;
}
}


So by defining our WUC as implementing IRenderableFBML< dtoPagedGrid > we are telling .NET to expect our control to implement a method named PopulateData and that this method will accept a single parameter of type dtoPagedGrid. It is not necessary that a WUC or Page implement IRenderableFBML< dtoPagedGrid > . However, if it does, the FBML renderer utility will invoke the PopulateData method and pass it the data you supply at run-time.

In this control’s PopulateData method we set the paging control’s current page number property, and we bind the GridView to the data collection, which is the current page of data. Also note our WUC would read from the database a total number of records. The paging control needs this value so that it can calculate the total number of pages. I have omitted that code since it is largely irrelevant to this example.

Now we have described our parent page, and the WUC that holds the grid that will be shown on that page. We have seen how they are linked via the name of the javascript function, and we have seen how the grid gets bound to its data. All that’s left now is to tie it all together inside our Generic Handler, AjaxTestHandler.ashx. If you’ve never used generic handlers before, they are interesting and I suggest reading up on them. The usage here is very simple, I will not be defining any file extensions or HTTP verbs and all that! I am using the handler to capture the request from the FB servers since they require no page presentation - only an FBML return block.

Let’s look at the handler module now:

(entire code for AjaxTestHandler.ashx)


<%@ WebHandler Language="C#" Class="AjaxTestHandler" %>
using System;
using System.Web;
using System.Collections.Generic;
using facebook.web;

public class AjaxTestHandler : IHttpHandler {

public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
String renderedFBML= "";
long newPageNum;
long.TryParse(context.Request.Params["pageNum"], out newPageNum);
Dictionary< Thing > things =
ThingData.readAllThingsPaged(newPageNum, 10);
dtoPagedGrid dto = new dtoPagedGrid(things, newPageNum);
renderedFBML = FBMLControlRenderer.RenderFBML< dtoPagedGrid >
("~/wucAjaxGrid.ascx", dto);
context.Response.Write(renderedFBML);
}
public bool IsReusable
{
get { return false; }
}

}


First, get the page number from the request parameters. Next we read the single page of data needed using the SQL method defined in a previous article. Then, we take the page number and the page data collection and stuff it into the dtoPagedGrid object. And now, the rendering of the FBML!

FBMLControlRenderer.RenderFBML is part of the Facebook Development Toolkit. It is a Generic method, and here we are of course binding it to dtoPagedGrid. We supply the name of the object to be rendered (our web user control) and the object to be handed to the WUC via the PopulateData method (dtoPagedGrid). The string renderedFBML is populated with the entire client-side-ready markup that this WUC produces (our grid and pager bound to the current data). We simply write it to the Response. The Facebook server then takes this block of FBML markup, and passes it to the ondone function, where it is set into the contents of the DIV.

That brings us full-circle on the Ajaxified Pager Control.

I hope this has been helpful as you build Facebook .NET applications.

Don't hesitate to post questions here, at the Codeplex project, or by emailing me at the links provided.

-David

1 comment:

  1. Wow this site is really amazing and informative to me, i am really glade that this site is pretty cool as well as its stuff.

    ReplyDelete