Developing facebookbapplications phần 9
- 19 trang
- file .pdf
A JAX IN FBJS 154
That code should look familiar to you. It’s the same code we would
use for a normal Rails application. Facebooker takes care of hiding the
Facebook-specific details from you.
We’re now using real Ajax to do the same thing we did previously with
Mock Ajax. Facebooker made the normal Rails helpers work for us, but
only in a very simple case. To do much more using Ajax, we’re going to
have to look at the guts of the Facebook Ajax library.
Ajax Under the Covers
Let’s take a look at how Ajax is implemented by Facebook. Facebook
provides an Ajax5 object as an interface to the browser’s XmlHTTPRequest
object. It provides a simple abstraction to your application, as you can
see in the following code:
var ajax=new Ajax();
ajax.responseType=Ajax.JSON;
ajax.ondone = function(data) {
// do something with the data
}
ajax.post('http://www.example.com/comments' ,
{"body" : "This is a comment" ,"receiver_id" : 4});
To make an Ajax request, we perform four steps:
1. Create an Ajax instance.
2. Choose a response type.
3. Provide a callback.
4. Call ajax.post.
We can create a new Ajax object using var ajax=new Ajax();. Next, we
decide what type of data we will request. We can choose to receive the
response as FBML, JSON, or raw content. If we choose FBML, Facebook
will process the data into a format that can be passed to setInnerFBML. If
we choose raw content, we could receive either plain text or XHTML to
pass to the setTextValue or setInnerXHTML methods, respectively.
Once we’ve picked the type of data, we must give Facebook a method to
call when the request is complete. We do this by setting the ajax.ondone
method with a function reference. Usually, this method will do some-
thing with the data such as update the page. We can optionally pro-
vide a method to call when an error occurs by setting the ajax.onerror
attribute. Finally, we call the ajax.post method to tell Facebook to start
the request. There shouldn’t be many times when you need to revert to
5. The documentation is at http://wiki.developers.facebook.com/index.php/FBJS#Ajax.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A JAX IN FBJS 155
writing Ajax calls by hand, but understanding how they work can help
you debug problems as they come up.
Using JSON with Ajax
So far, we’ve been limited to updating only one element at a time using
Ajax. Let’s look at how we can use JSON to update both the list of
comments and also a message element with a single request. We’ll start
by changing our call to remote_form_for to make a request for JSON.
To do this, we need to replace the update parameter with a success
callback. Our success parameter is a JavaScript snippet that receives
the JSON-formatted data in a variable named request:
<% remote_form_for Comment.new,
:url=>comments_url(:canvas=>false),
:success=>"update_multiple(request)" do |f|%>
<%= text_area_tag :body, "" ,
:onchange=>"update_count(this.getValue(),'remaining');" ,
:onkeyup=>"update_count(this.getValue(),'remaining');"
%>
With that done, we have two steps left to complete. We’ll need to imple-
ment the update_multiple method and change our controller. Let’s start
with the controller method. We know that our update_multiple method
will need to set the content of two DOM elements. We’ve used the set-
TextValue and setInnerXHTML methods in the past, but neither of those will
work here. Our comment list includes several FBML tags that need to be
expanded. We need some way of asking Facebook to convert a response
from FBML to something we can pass to setInnerFBML. By convention,
Facebook will treat any content in a variable whose name starts with
fbml as an FBML document to be interpreted. Let’s send back a JSON
object with a couple of different fields. We’ll include a list of IDs to be
updated along with the content for each ID:
def create
comment_receiver = User.find(params[:comment_receiver])
current_user.comment_on(comment_receiver,params[:body])
if request.xhr?
@comments=comment_receiver.comments(true)
render :json=>{:ids_to_update=>[:all_comments,:form_message],
:fbml_all_comments=>render_to_string(:partial=>"comments" ),
:fbml_form_message=>"Your comment has been added." }
else
redirect_to battles_path(:user_id=>comment_receiver.id)
end
end
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A JAX IN FBJS 156
Now we need to build the update_multiple method, which needs to loop
through the list of IDs to be updated and then set the FBML for each
element:
function update_multiple(json) {
for( var i=0; i id=json["ids_to_update" ][i];
$(id).setInnerFBML(json["fbml_" +id]);
}
}
That’s all there is to it. You could do quite a bit more using JSON. JSON
is a great way of sending complex data to a JavaScript method on your
page. For example, you could build a chat application that sends new
messages as a JavaScript array.
Using fb:js-string
We’ve now looked at two different ways to use the setInnerFBML method.
Not only can you pass it the results of an Ajax call of Ajax.FBML type or a
string processed as FBML from a JSON request. You can also pass it an
. is an FBML tag that creates a JavaScript
variable. When Facebook processes the page, it turns the content of the
tag into a JavaScript variable that can be passed to setInnerFBML.
These strings are used when you want to store some FBML that may be
used later. For instance, if you show a number of thumbnails of images
and want to show a larger image when the thumbnail is clicked, you
could store the FBML for displaying the larger image in a.
Then, you can swap the content of the main display without having to
go back to the server.
<%=image_tag photo.public_filename(:large)%>
<%=photo.caption%>
<%= image_tag photo.public_filename(:thumbnail),
:onclick=>"$('photos').setInnerFBML(photo_#{photo.id}_large);" %>
It is important to note that this tag creates an actual JavaScript vari-
able. That means when you want to reference the result, you don’t sur-
round the name in quotes. For a photo with an ID of 7, this creates a
variable named photo_7_large.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
S UMMARY 157
7.3 Summary
We’ve walked through a brief tour of FBJS. As you can see, it isn’t
as powerful as regular JavaScript, but it still can help you make your
application more dynamic. FBJS is a relatively new feature, so expect
it to continue to evolve over time.
Next, we’ll look at how we can integrate our application with existing
websites.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
Chapter 8
Integrating Your App
with Other Websites
So far, we’ve looked at making Karate Poke work only inside Facebook.
Sometimes we’ll want our application to be available outside Facebook.
For instance, you may want to create a marketing page that people can
view without having Facebook accounts. You also might want to take
an existing application and make it available via Facebook. Finally, you
may want to take advantage of features that aren’t available through
the canvas, such as image uploads or advanced JavaScript. We’re in
the homestretch now. This is the last new functionality we will add to
Karate Poke!
We’ll use Karate Poke to look at how to implement all this functionality.
We’ll start by creating KaratePoke.com, a site to promote our application.
Next, we’ll create a leaderboard that can be viewed both through Face-
book and outside Facebook. We’ll look at some of the special issues
involved in sharing information. Next, we’ll look at how to integrate
Facebook with an existing application. We’ll close by looking at how we
can use the Facebook JavaScript library to get Facebook functionality
outside the Facebook canvas.
8.1 Making Content Accessible
Let’s start with the simplest case. Let’s create a marketing site for
Karate Poke. Our marketing page will explain what Karate Poke is and
encourage users to join Facebook to play. We want this page to be avail-
able at http://www.karatepoke.com. Since this page is marketing for our
application, we don’t want it to appear inside the Facebook canvas.
Prepared exclusively for Alison Tyler
M AKING C ONTENT A CCESSIBLE 159
Let’s get started by creating a marketing controller. After we create the
controller, we’ll need some view files. You’re welcome to create your own
marketing page for Karate Poke. Alternatively, you can copy the ver-
sions I created from chapter8/karate_poke/app/views/marketing into your
own views directory. With our views in place, we need to map the root
URL to our marketing controller. That’s done with the following entry
in routes.rb:
map.connect '' , :controller=>"marketing"
That’s a problem. We’re already using the default route for our battles
page. We need a way to tell Rails to use one action for Facebook requests
and another action for non-Facebook requests. Rails provides the :con-
ditions option on routes to allow you to specify conditions that must be
met for a route to be used. By default, you can make routes condi-
tional only upon HTTP methods. Facebooker extends this functionality
to include conditions about whether a request is from the Facebook
canvas:1
map.battles '' ,:controller=>"attacks" ,
:conditions=>{:canvas=>true}
map.marketing_home '' ,:controller=>"marketing" ,
:conditions=>{:canvas=>false}
When a request comes in, Rails will look at whether the request is
coming from Facebook or directly from a web browser. It does this by
looking for the fb_sig_in_canvas and fb_sig_ajax parameters. If one of those
exists, then the request is a Facebook request. Rails starts at the top
of the routes.rb file and looks for a matching route. Since Rails matches
from the top down, you should always make sure your most specific
route is first. Let’s consider the following route:
map.battles '' ,:controller=>"attacks"
map.marketing_home '' ,:controller=>"marketing" ,
:conditions=>{:canvas=>false}
The first route will match all requests for the default route, so no
requests will ever be sent to our marketing controller. If instead we were
to reverse the order, non-Facebook requests would go to our marketing
page, and Facebook requests would be sent to the battles page:
Download chapter8/karate_poke/config/routes.rb
map.battles '' ,:controller=>"attacks" ,
:conditions=>{:canvas=>true}
map.marketing_home '' ,:controller=>"marketing"
1. You can read about how this works in an article by Jamis Buck at
http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CTIONS T HAT W ORK B OTH WAYS 160
With those routes, we almost have a functioning marketing page. Due
to our ensure_authenticated... filter, our application will redirect view-
ers to the Facebook application install page. To make our marketing
page visible outside Facebook, we will need to skip that filter using
skip_before_filter :ensure_authenticated_to_facebook. If only a small section
of your application is used inside Facebook, you probably want to move
the call to ensure_authenticated_to_facebook from the ApplicationController
to the controllers that handle Facebook requests.
Using conditional routing works nicely when we want two pages with
the same URL to have different functionality. Next, we’ll look at using
the same logic with different displays.
8.2 Actions That Work Both Ways
Sometimes we want to do more than have different actions for the same
URL. Sometimes we want to have the same logic and just have differ-
ent displays. We’re going to add a little more content to our Karate
Poke marketing site. We will build a leaderboard, a list of the top users
ordered by the number of successful battles they’ve engaged in. We’ll
look at how we can use one controller action with different displays for
Facebook and non-Facebook requests.
By now, you could probably build the Facebook version in your sleep.
We can create a LeadersController and add a route to routes.rb:
map.resources :leaders
With our routing in place, we just need an action and a view:
Download chapter8/karate_poke/app/controllers/leaders_controller.rb
def index
@leaders = User.paginate(:order=>"total_hits desc" ,
:page=>(params[:page]||1))
end
Download chapter8/karate_poke/app/views/leaders/index.fbml.erb
<%= will_paginate @leaders%>
That’s nothing new to us. Now we just need to figure out how to make
that available to non-Facebook users. You’ve probably noticed that we
have been creating .fbml.erb templates for all our views. Rails will try to
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 161
send back the right content type for each request. That means support-
ing both Facebook and non-Facebook requests in a single action can be
as simple as creating another template with a different file extension.
If we build an .html.erb template, Rails will use that for non-Facebook
requests. Here is a version of our leaderboard that we can use outside
Facebook:
<%= will_paginate @leaders%>
To view the HTML version of our leaders page, go to /leaders using your
callback URL as the host. When you do, you’ll be redirected to the
Facebook application install page. We forgot to tell Facebooker that our
users don’t need to be logged in to view this page. If we add skip_before_
filter :ensure_authenticated_to_facebook to the beginning of our controller,
we should be able to view our page. This is something we’ll need to do
on every page that is visible outside Facebook.
As you can see, this looks similar to the Facebook version of the page.
Since web browsers don’t understand FBML, we had to remove our
code that rendered an tag. In fact, we don’t have any way of
displaying our users’ names. Facebook won’t let us store their names
in our database, and we can’t make an API request to retrieve them,
since non-Facebook requests don’t have a Facebook session.
This is a tricky problem. It’s nice that Facebook gives us access to a
wealth of information about our users, but it locks us in to Facebook.
Different applications will solve this problem in different ways. If your
application already exists outside Facebook, you may be able to use
your existing data. In our case, we really need a name to show for each
user. We’ll look at fixing that next.
8.3 Handling Facebook-Specific Data
I wish there was some magic bullet I could give you to make handling
Facebook-specific data easy. Unfortunately, I can’t. The cost of getting
access to the wealth of Facebook data is that it can be used only inside
the context of Facebook. In our case, we don’t need much information
from our users outside the canvas; all we need is a name for each user.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 162
Figure 8.1: Asking for data to be shown outside Facebook
Although Facebook limits what API data we can store, it doesn’t limit
what information we can get from our users. If we want to display a
name outside Facebook, we can simply ask for it. We can even have a
little fun with this. We can ask our users to give us a nickname that
they want displayed. We can use this nickname both outside Facebook
and as a replacement for “a hidden ninja.”
We’ll start by adding a nickname field to our users table. After we’ve
created and run that migration, we’ll need to build a form. Since we’ll
need to gather this data from all our existing users, we should put our
form front and center. Let’s add it to our battles page. Once a user has
set their nickname, we can hide the form and use the Ajax we learned
in the previous chapter to show it on demand. You can see what we’re
going to build in Figure 8.1.
Let’s start by creating a simple controller method. Since we are going
to be updating a user object, let’s create a users controller. Make sure
you set it up as a resource in routes.rb. We will use the update action to
perform the update.
Download chapter8/karate_poke/app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
saved = current_user.update_attribute(:nickname,params[:nickname])
# the update was a success, show the closed_form
render :partial=>"nickname" , :locals=>{:closed=>saved}
end
end
Next, we’ll need a view. Our view will serve two purposes. We’ll want it to
show the nickname form when a user hasn’t set their nickname. We’ll
also use it to display their nickname once they have one. By passing
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 163
in a local variable to our view, we can control whether to show the
nickname form, as you can see here:
Download chapter8/karate_poke/app/views/users/_nickname.erb
Now we just need to add that to our battles view:
<% fb_if_is_user @user do %>
<%= render :partial=>"users/nickname" ,
:locals => { :closed => !current_user.nickname.blank? }%>
<% fb_else do %>
...
Next, we can make our name helper use the nickname field. We also
need to create a helper for displaying names outside Facebook:
Download chapter8/karate_poke/app/helpers/application_helper.rb
def name(user,options={})
fb_name(user,
{:ifcantsee=>(user.nickname||"a hidden ninja" )}.merge(options))
end
def external_name(user)
user.nickname || "a hidden ninja"
end
There’s just one final step in this process. We need to change our
leaderboard to use the external_name helper when we are outside Face-
book:
<%= will_paginate @leaders%>
That was quite a bit of work for just one piece of data. This works when
we need information about a user, but it would be nearly impossible to
do the same thing with an event. After all, events can change outside
our application, and there would be no way to keep our information up-
to-date. As you can see, the cost of access to Facebook’s data is being
locked in to using it through its site.
8.4 Sharing Sessions
We’ve seen how to make our application available to both Facebook and
non-Facebook requests. At times we may need to move between Face-
book requests and non-Facebook requests. For instance, since Face-
book doesn’t allow multipart form posts, we’ll need to bypass Facebook
for image uploads. We also may want to bypass Facebook to use more
advanced JavaScript functionality, such as drag and drop.
In most cases, we can solve this problem using the tag.
An iframe, or inline frame, is an HTML component that embeds content
from a URL into an element within another page. The content included
in the iframe is loaded by the browser from the remote server and
can include any content that an HTML page can contain. By including
an iframe in your canvas page, you can embed regular HTML content
including Flash, JavaScript, or even Java.
The FBML tag creates an iframe inside our canvas. It takes
a single parameter, url, which is the URL on our server that will provide
the content for the frame. Make sure you specify a URL with :canvas =>
false so that the request goes directly to your server. Facebook modifies
the URL you provide to to include all the normal Facebook
parameters, including fb_sig_session_key. This means the requested page
will have access to the facebook_session of the viewer. In fact, because
of the way Facebooker implements sessions, once a session has been
established with an, all direct requests from that user’s
browser will maintain that session.
We will have to make one change to make session sharing work. When
we originally configured our application, we used the Rails cookie ses-
sion store. Because our application’s canvas and iframe pages have dif-
ferent URLs, our users’ browsers won’t allow us to use the same cookie
for both. To work around this, we can use a different type of session.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 165
We’ll use the ActiveRecord store to keep our session information in the
database.
First, we’ll need to run script/generate session_migration to create the ses-
sions table. You should also run rake db:migrate to execute the migration.
Next, we’ll need to uncomment a line in environment.rb:
config.action_controller.session_store = :active_record_store
With that done, our session can be shared between the Facebook can-
vas and an iframe. We don’t need to use an iframe to share session
information, though. We can also join the session by including session
information in a link directly to our server. For example, If we wanted
to link from our FBML leaderboard to our HTML leaderboard without
sharing sessions, we could use this:
<%= link_to "HTML view" ,leaders_url(:canvas=>false)%>
If we wanted to do the same thing while maintaining the user’s session,
we would use this:
<%= link_to "HTML view" ,
leaders_url(facebook_session_parameters.merge(:canvas=>false))%>
By adding this parameter, we’re telling Facebooker which existing ses-
sion to use. Once the link between the Facebook session and the direct
session has been established, the link will continue to exist even with-
out sending facebook_session_parameters.
If we had an existing application with a login system, we could use an
iframe to link a user’s Facebook account to their existing information.
By having our user log in to our application within an, we
would have access to both their Facebook session and their existing
account information. Once we have their Facebook ID saved, we could
share information between a Facebook version of our application and a
non-Facebook version.
8.5 Accessing Facebook Outside the Canvas
We’ve looked at several ways of integrating Facebook and non-Facebook
applications. Everything we’ve looked at so far has focused on the server
side. If our application already exists entirely outside Facebook and we
want only basic integration, we have another option to consider.
Facebook has recently released a client-side JavaScript library that pro-
vides access to the Facebook API from any website. We could use the
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 166
Sharing Information with Facebook
We’ve spent a lot of time looking at all the information that
Facebook can offer us. Don’t limit yourself to just this informa-
tion. If you have an existing application, make use of your exist-
ing data. For instance, the Ultimatums application allows users
to share data between their Facebook application and its exist-
ing website.
Similarly, the Twitter application allows users to update their
Facebook status by sending a message to Twitter. Communica-
tion doesn’t need to be a one-way street. Don’t just integrate
your application with Facebook; use the platform to integrate
Facebook with your existing application.
JavaScript API to give us access to our users’ social graphs or even to
send notifications from the browser.
The Facebook JavaScript API requires some configuration before it can
be used. For security purposes, web browsers limit JavaScript to mak-
ing requests only to the originating server.
Since the Facebook API needs to communicate with Facebook, we’ll
need to help it work around the browser security model. To do this,
we’ll create a file called xd_receiver.html in our public directory. The file
should have the following content:
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
Cross-Domain Receiver Page
Now that we have that page in place, we can create a page that uses
the Facebook API library. Let’s use our marketing page to experiment
with the library. We’ll start by including it into our page.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 167
Figure 8.2: Facebook JS API showing a user
That takes only one line of code:
To use the library, we’ll need to create an instance of the FB.ApiClient.
Its constructor takes two parameters, our application’s API key and the
server path to our xd_receiver.html file. Before we can make API calls,
we’ll need to make sure our user is authenticated to Facebook. To do
this, we’ll call the requireLogin method. If this is the first time we’re using
the API for a user, they will be redirected to a Facebook login screen
where they are prompted to enter their login information. Once they
have authenticated with Facebook, they will be sent back to our site
where execution will continue.
Since the JavaScript API requires our user to be authenticated with
Facebook, the requireLogin method requires that a callback function be
provided. This callback will be called once authentication is complete.
You’ll see extensive use of callback functions inside the API client. In
general, we want all our code that depends upon the Facebook API to be
called from this callback. The following example opens an alert dialog
box containing the Facebook ID of the logged-in user; give it a try:
// Create an ApiClient object, passing app's API key and
// a site relative URL to xd_receiver.htm
var api = new FB.ApiClient('<%=Facebooker.api_key%>' ,
'/xd_receiver.html' ,
null);
api.requireLogin(function(exception) {
alert("Current user id is " + api.get_session().uid);
});
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 168
I’ve had good luck getting that code to work in Firefox, but not in
Safari for the Mac. The problem seems to occur only when working
using script/server. I haven’t seen the same issues once the application
is deployed. You’ll want to keep this in mind while you are doing your
development.
We’ve seen how to get the Facebook ID of the current user. Now let’s
take a look at how to get the user’s name. We’ll use the users.getInfo API
call.2 Our code will need to make a call to this API function and pass in
three parameters: the Facebook ID of the person whose name we want,
the fields we want to retrieve, and a callback to be called when the
request completes. The following is an example. If you run that code,
the result should look like Figure 8.2, on the previous page.
api.requireLogin(function(exception) {
// Get the name of the current user
api.users_getInfo(api.get_session().uid,
["first_name" ],
function(result, exception) {
Debug.dump(result, 'Get first name of user ' );
});
});
In the previous code, we display the result of our API call using the
Debug.dump method. This method writes data to the browser’s Java-
Script console. If you would rather see the result on your web page,
you can include a text area with the ID _traceTextBox like in the following
code:
This code is very different from our Ruby code that does the same thing,
user.first_name. Along with having to deal with callback functions, we also
have to deal with an awkward naming convention. You’ll find that these
method names very closely match the ones used in the official Facebook
PHP client.
We’re not limited to such basic data retrieval. We can make much more
complicated calls. For instance, we could get the names of all a user’s
friends by nesting an API call inside our callback.
2. You can find documentation at http://wiki.developers.facebook.com/index.php/Users.getInfo.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
That code should look familiar to you. It’s the same code we would
use for a normal Rails application. Facebooker takes care of hiding the
Facebook-specific details from you.
We’re now using real Ajax to do the same thing we did previously with
Mock Ajax. Facebooker made the normal Rails helpers work for us, but
only in a very simple case. To do much more using Ajax, we’re going to
have to look at the guts of the Facebook Ajax library.
Ajax Under the Covers
Let’s take a look at how Ajax is implemented by Facebook. Facebook
provides an Ajax5 object as an interface to the browser’s XmlHTTPRequest
object. It provides a simple abstraction to your application, as you can
see in the following code:
var ajax=new Ajax();
ajax.responseType=Ajax.JSON;
ajax.ondone = function(data) {
// do something with the data
}
ajax.post('http://www.example.com/comments' ,
{"body" : "This is a comment" ,"receiver_id" : 4});
To make an Ajax request, we perform four steps:
1. Create an Ajax instance.
2. Choose a response type.
3. Provide a callback.
4. Call ajax.post.
We can create a new Ajax object using var ajax=new Ajax();. Next, we
decide what type of data we will request. We can choose to receive the
response as FBML, JSON, or raw content. If we choose FBML, Facebook
will process the data into a format that can be passed to setInnerFBML. If
we choose raw content, we could receive either plain text or XHTML to
pass to the setTextValue or setInnerXHTML methods, respectively.
Once we’ve picked the type of data, we must give Facebook a method to
call when the request is complete. We do this by setting the ajax.ondone
method with a function reference. Usually, this method will do some-
thing with the data such as update the page. We can optionally pro-
vide a method to call when an error occurs by setting the ajax.onerror
attribute. Finally, we call the ajax.post method to tell Facebook to start
the request. There shouldn’t be many times when you need to revert to
5. The documentation is at http://wiki.developers.facebook.com/index.php/FBJS#Ajax.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A JAX IN FBJS 155
writing Ajax calls by hand, but understanding how they work can help
you debug problems as they come up.
Using JSON with Ajax
So far, we’ve been limited to updating only one element at a time using
Ajax. Let’s look at how we can use JSON to update both the list of
comments and also a message element with a single request. We’ll start
by changing our call to remote_form_for to make a request for JSON.
To do this, we need to replace the update parameter with a success
callback. Our success parameter is a JavaScript snippet that receives
the JSON-formatted data in a variable named request:
<% remote_form_for Comment.new,
:url=>comments_url(:canvas=>false),
:success=>"update_multiple(request)" do |f|%>
<%= text_area_tag :body, "" ,
:onchange=>"update_count(this.getValue(),'remaining');" ,
:onkeyup=>"update_count(this.getValue(),'remaining');"
%>
With that done, we have two steps left to complete. We’ll need to imple-
ment the update_multiple method and change our controller. Let’s start
with the controller method. We know that our update_multiple method
will need to set the content of two DOM elements. We’ve used the set-
TextValue and setInnerXHTML methods in the past, but neither of those will
work here. Our comment list includes several FBML tags that need to be
expanded. We need some way of asking Facebook to convert a response
from FBML to something we can pass to setInnerFBML. By convention,
Facebook will treat any content in a variable whose name starts with
fbml as an FBML document to be interpreted. Let’s send back a JSON
object with a couple of different fields. We’ll include a list of IDs to be
updated along with the content for each ID:
def create
comment_receiver = User.find(params[:comment_receiver])
current_user.comment_on(comment_receiver,params[:body])
if request.xhr?
@comments=comment_receiver.comments(true)
render :json=>{:ids_to_update=>[:all_comments,:form_message],
:fbml_all_comments=>render_to_string(:partial=>"comments" ),
:fbml_form_message=>"Your comment has been added." }
else
redirect_to battles_path(:user_id=>comment_receiver.id)
end
end
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A JAX IN FBJS 156
Now we need to build the update_multiple method, which needs to loop
through the list of IDs to be updated and then set the FBML for each
element:
function update_multiple(json) {
for( var i=0; i
$(id).setInnerFBML(json["fbml_" +id]);
}
}
That’s all there is to it. You could do quite a bit more using JSON. JSON
is a great way of sending complex data to a JavaScript method on your
page. For example, you could build a chat application that sends new
messages as a JavaScript array.
Using fb:js-string
We’ve now looked at two different ways to use the setInnerFBML method.
Not only can you pass it the results of an Ajax call of Ajax.FBML type or a
string processed as FBML from a JSON request. You can also pass it an
variable. When Facebook processes the page, it turns the content of the
tag into a JavaScript variable that can be passed to setInnerFBML.
These strings are used when you want to store some FBML that may be
used later. For instance, if you show a number of thumbnails of images
and want to show a larger image when the thumbnail is clicked, you
could store the FBML for displaying the larger image in a
Then, you can swap the content of the main display without having to
go back to the server.
<%=image_tag photo.public_filename(:large)%>
<%=photo.caption%>
<%= image_tag photo.public_filename(:thumbnail),
:onclick=>"$('photos').setInnerFBML(photo_#{photo.id}_large);" %>
It is important to note that this tag creates an actual JavaScript vari-
able. That means when you want to reference the result, you don’t sur-
round the name in quotes. For a photo with an ID of 7, this creates a
variable named photo_7_large.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
S UMMARY 157
7.3 Summary
We’ve walked through a brief tour of FBJS. As you can see, it isn’t
as powerful as regular JavaScript, but it still can help you make your
application more dynamic. FBJS is a relatively new feature, so expect
it to continue to evolve over time.
Next, we’ll look at how we can integrate our application with existing
websites.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
Chapter 8
Integrating Your App
with Other Websites
So far, we’ve looked at making Karate Poke work only inside Facebook.
Sometimes we’ll want our application to be available outside Facebook.
For instance, you may want to create a marketing page that people can
view without having Facebook accounts. You also might want to take
an existing application and make it available via Facebook. Finally, you
may want to take advantage of features that aren’t available through
the canvas, such as image uploads or advanced JavaScript. We’re in
the homestretch now. This is the last new functionality we will add to
Karate Poke!
We’ll use Karate Poke to look at how to implement all this functionality.
We’ll start by creating KaratePoke.com, a site to promote our application.
Next, we’ll create a leaderboard that can be viewed both through Face-
book and outside Facebook. We’ll look at some of the special issues
involved in sharing information. Next, we’ll look at how to integrate
Facebook with an existing application. We’ll close by looking at how we
can use the Facebook JavaScript library to get Facebook functionality
outside the Facebook canvas.
8.1 Making Content Accessible
Let’s start with the simplest case. Let’s create a marketing site for
Karate Poke. Our marketing page will explain what Karate Poke is and
encourage users to join Facebook to play. We want this page to be avail-
able at http://www.karatepoke.com. Since this page is marketing for our
application, we don’t want it to appear inside the Facebook canvas.
Prepared exclusively for Alison Tyler
M AKING C ONTENT A CCESSIBLE 159
Let’s get started by creating a marketing controller. After we create the
controller, we’ll need some view files. You’re welcome to create your own
marketing page for Karate Poke. Alternatively, you can copy the ver-
sions I created from chapter8/karate_poke/app/views/marketing into your
own views directory. With our views in place, we need to map the root
URL to our marketing controller. That’s done with the following entry
in routes.rb:
map.connect '' , :controller=>"marketing"
That’s a problem. We’re already using the default route for our battles
page. We need a way to tell Rails to use one action for Facebook requests
and another action for non-Facebook requests. Rails provides the :con-
ditions option on routes to allow you to specify conditions that must be
met for a route to be used. By default, you can make routes condi-
tional only upon HTTP methods. Facebooker extends this functionality
to include conditions about whether a request is from the Facebook
canvas:1
map.battles '' ,:controller=>"attacks" ,
:conditions=>{:canvas=>true}
map.marketing_home '' ,:controller=>"marketing" ,
:conditions=>{:canvas=>false}
When a request comes in, Rails will look at whether the request is
coming from Facebook or directly from a web browser. It does this by
looking for the fb_sig_in_canvas and fb_sig_ajax parameters. If one of those
exists, then the request is a Facebook request. Rails starts at the top
of the routes.rb file and looks for a matching route. Since Rails matches
from the top down, you should always make sure your most specific
route is first. Let’s consider the following route:
map.battles '' ,:controller=>"attacks"
map.marketing_home '' ,:controller=>"marketing" ,
:conditions=>{:canvas=>false}
The first route will match all requests for the default route, so no
requests will ever be sent to our marketing controller. If instead we were
to reverse the order, non-Facebook requests would go to our marketing
page, and Facebook requests would be sent to the battles page:
Download chapter8/karate_poke/config/routes.rb
map.battles '' ,:controller=>"attacks" ,
:conditions=>{:canvas=>true}
map.marketing_home '' ,:controller=>"marketing"
1. You can read about how this works in an article by Jamis Buck at
http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CTIONS T HAT W ORK B OTH WAYS 160
With those routes, we almost have a functioning marketing page. Due
to our ensure_authenticated... filter, our application will redirect view-
ers to the Facebook application install page. To make our marketing
page visible outside Facebook, we will need to skip that filter using
skip_before_filter :ensure_authenticated_to_facebook. If only a small section
of your application is used inside Facebook, you probably want to move
the call to ensure_authenticated_to_facebook from the ApplicationController
to the controllers that handle Facebook requests.
Using conditional routing works nicely when we want two pages with
the same URL to have different functionality. Next, we’ll look at using
the same logic with different displays.
8.2 Actions That Work Both Ways
Sometimes we want to do more than have different actions for the same
URL. Sometimes we want to have the same logic and just have differ-
ent displays. We’re going to add a little more content to our Karate
Poke marketing site. We will build a leaderboard, a list of the top users
ordered by the number of successful battles they’ve engaged in. We’ll
look at how we can use one controller action with different displays for
Facebook and non-Facebook requests.
By now, you could probably build the Facebook version in your sleep.
We can create a LeadersController and add a route to routes.rb:
map.resources :leaders
With our routing in place, we just need an action and a view:
Download chapter8/karate_poke/app/controllers/leaders_controller.rb
def index
@leaders = User.paginate(:order=>"total_hits desc" ,
:page=>(params[:page]||1))
end
Download chapter8/karate_poke/app/views/leaders/index.fbml.erb
<%= will_paginate @leaders%>
- <%=name leader%>: <%=leader.total_hits%>
<% for leader in @leaders %>
<% end %>
That’s nothing new to us. Now we just need to figure out how to make
that available to non-Facebook users. You’ve probably noticed that we
have been creating .fbml.erb templates for all our views. Rails will try to
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 161
send back the right content type for each request. That means support-
ing both Facebook and non-Facebook requests in a single action can be
as simple as creating another template with a different file extension.
If we build an .html.erb template, Rails will use that for non-Facebook
requests. Here is a version of our leaderboard that we can use outside
Facebook:
<%= will_paginate @leaders%>
- Unknown: <%=leader.total_hits%>
<% for leader in @leaders %>
<% end %>
To view the HTML version of our leaders page, go to /leaders using your
callback URL as the host. When you do, you’ll be redirected to the
Facebook application install page. We forgot to tell Facebooker that our
users don’t need to be logged in to view this page. If we add skip_before_
filter :ensure_authenticated_to_facebook to the beginning of our controller,
we should be able to view our page. This is something we’ll need to do
on every page that is visible outside Facebook.
As you can see, this looks similar to the Facebook version of the page.
Since web browsers don’t understand FBML, we had to remove our
code that rendered an
displaying our users’ names. Facebook won’t let us store their names
in our database, and we can’t make an API request to retrieve them,
since non-Facebook requests don’t have a Facebook session.
This is a tricky problem. It’s nice that Facebook gives us access to a
wealth of information about our users, but it locks us in to Facebook.
Different applications will solve this problem in different ways. If your
application already exists outside Facebook, you may be able to use
your existing data. In our case, we really need a name to show for each
user. We’ll look at fixing that next.
8.3 Handling Facebook-Specific Data
I wish there was some magic bullet I could give you to make handling
Facebook-specific data easy. Unfortunately, I can’t. The cost of getting
access to the wealth of Facebook data is that it can be used only inside
the context of Facebook. In our case, we don’t need much information
from our users outside the canvas; all we need is a name for each user.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 162
Figure 8.1: Asking for data to be shown outside Facebook
Although Facebook limits what API data we can store, it doesn’t limit
what information we can get from our users. If we want to display a
name outside Facebook, we can simply ask for it. We can even have a
little fun with this. We can ask our users to give us a nickname that
they want displayed. We can use this nickname both outside Facebook
and as a replacement for “a hidden ninja.”
We’ll start by adding a nickname field to our users table. After we’ve
created and run that migration, we’ll need to build a form. Since we’ll
need to gather this data from all our existing users, we should put our
form front and center. Let’s add it to our battles page. Once a user has
set their nickname, we can hide the form and use the Ajax we learned
in the previous chapter to show it on demand. You can see what we’re
going to build in Figure 8.1.
Let’s start by creating a simple controller method. Since we are going
to be updating a user object, let’s create a users controller. Make sure
you set it up as a resource in routes.rb. We will use the update action to
perform the update.
Download chapter8/karate_poke/app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
saved = current_user.update_attribute(:nickname,params[:nickname])
# the update was a success, show the closed_form
render :partial=>"nickname" , :locals=>{:closed=>saved}
end
end
Next, we’ll need a view. Our view will serve two purposes. We’ll want it to
show the nickname form when a user hasn’t set their nickname. We’ll
also use it to display their nickname once they have one. By passing
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
H ANDLING F ACEBOOK -S PECIFIC D ATA 163
in a local variable to our view, we can control whether to show the
nickname form, as you can see here:
Download chapter8/karate_poke/app/views/users/_nickname.erb
<% if closed %> style="display:none" <% end %> >
<% remote_form_for :user,current_user,
:url=>user_url(:id=>current_user,:canvas=>false),
:html=>{:method=>:put},
:update=>"nickname" do |f| %>
Select a nickname to show outside of Facebook
<%= text_field_tag :nickname,current_user.nickname%>
<%= submit_tag "save" %>
<% end %>
<% remote_form_for :user,current_user,
:url=>user_url(:id=>current_user,:canvas=>false),
:html=>{:method=>:put},
:update=>"nickname" do |f| %>
Select a nickname to show outside of Facebook
<%= text_field_tag :nickname,current_user.nickname%>
<%= submit_tag "save" %>
<% end %>
<% unless closed %> style="display:none" <% end %>>
Your nickname is <%=current_user.nickname%>
<%=link_to_function "(change it)" ,
"$('nickname_open').show();$('nickname_closed').hide()" %>
Your nickname is <%=current_user.nickname%>
<%=link_to_function "(change it)" ,
"$('nickname_open').show();$('nickname_closed').hide()" %>
Now we just need to add that to our battles view:
<% fb_if_is_user @user do %>
<%= render :partial=>"users/nickname" ,
:locals => { :closed => !current_user.nickname.blank? }%>
<% fb_else do %>
...
Next, we can make our name helper use the nickname field. We also
need to create a helper for displaying names outside Facebook:
Download chapter8/karate_poke/app/helpers/application_helper.rb
def name(user,options={})
fb_name(user,
{:ifcantsee=>(user.nickname||"a hidden ninja" )}.merge(options))
end
def external_name(user)
user.nickname || "a hidden ninja"
end
There’s just one final step in this process. We need to change our
leaderboard to use the external_name helper when we are outside Face-
book:
<%= will_paginate @leaders%>
- <%= external_name leader %>: <%=leader.total_hits%>
<% for leader in @leaders %>
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
S HARING S ESSIONS 164
<% end %>
That was quite a bit of work for just one piece of data. This works when
we need information about a user, but it would be nearly impossible to
do the same thing with an event. After all, events can change outside
our application, and there would be no way to keep our information up-
to-date. As you can see, the cost of access to Facebook’s data is being
locked in to using it through its site.
8.4 Sharing Sessions
We’ve seen how to make our application available to both Facebook and
non-Facebook requests. At times we may need to move between Face-
book requests and non-Facebook requests. For instance, since Face-
book doesn’t allow multipart form posts, we’ll need to bypass Facebook
for image uploads. We also may want to bypass Facebook to use more
advanced JavaScript functionality, such as drag and drop.
In most cases, we can solve this problem using the
An iframe, or inline frame, is an HTML component that embeds content
from a URL into an element within another page. The content included
in the iframe is loaded by the browser from the remote server and
can include any content that an HTML page can contain. By including
an iframe in your canvas page, you can embed regular HTML content
including Flash, JavaScript, or even Java.
The
a single parameter, url, which is the URL on our server that will provide
the content for the frame. Make sure you specify a URL with :canvas =>
false so that the request goes directly to your server. Facebook modifies
the URL you provide to
parameters, including fb_sig_session_key. This means the requested page
will have access to the facebook_session of the viewer. In fact, because
of the way Facebooker implements sessions, once a session has been
established with an
browser will maintain that session.
We will have to make one change to make session sharing work. When
we originally configured our application, we used the Rails cookie ses-
sion store. Because our application’s canvas and iframe pages have dif-
ferent URLs, our users’ browsers won’t allow us to use the same cookie
for both. To work around this, we can use a different type of session.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 165
We’ll use the ActiveRecord store to keep our session information in the
database.
First, we’ll need to run script/generate session_migration to create the ses-
sions table. You should also run rake db:migrate to execute the migration.
Next, we’ll need to uncomment a line in environment.rb:
config.action_controller.session_store = :active_record_store
With that done, our session can be shared between the Facebook can-
vas and an iframe. We don’t need to use an iframe to share session
information, though. We can also join the session by including session
information in a link directly to our server. For example, If we wanted
to link from our FBML leaderboard to our HTML leaderboard without
sharing sessions, we could use this:
<%= link_to "HTML view" ,leaders_url(:canvas=>false)%>
If we wanted to do the same thing while maintaining the user’s session,
we would use this:
<%= link_to "HTML view" ,
leaders_url(facebook_session_parameters.merge(:canvas=>false))%>
By adding this parameter, we’re telling Facebooker which existing ses-
sion to use. Once the link between the Facebook session and the direct
session has been established, the link will continue to exist even with-
out sending facebook_session_parameters.
If we had an existing application with a login system, we could use an
iframe to link a user’s Facebook account to their existing information.
By having our user log in to our application within an
would have access to both their Facebook session and their existing
account information. Once we have their Facebook ID saved, we could
share information between a Facebook version of our application and a
non-Facebook version.
8.5 Accessing Facebook Outside the Canvas
We’ve looked at several ways of integrating Facebook and non-Facebook
applications. Everything we’ve looked at so far has focused on the server
side. If our application already exists entirely outside Facebook and we
want only basic integration, we have another option to consider.
Facebook has recently released a client-side JavaScript library that pro-
vides access to the Facebook API from any website. We could use the
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 166
Sharing Information with Facebook
We’ve spent a lot of time looking at all the information that
Facebook can offer us. Don’t limit yourself to just this informa-
tion. If you have an existing application, make use of your exist-
ing data. For instance, the Ultimatums application allows users
to share data between their Facebook application and its exist-
ing website.
Similarly, the Twitter application allows users to update their
Facebook status by sending a message to Twitter. Communica-
tion doesn’t need to be a one-way street. Don’t just integrate
your application with Facebook; use the platform to integrate
Facebook with your existing application.
JavaScript API to give us access to our users’ social graphs or even to
send notifications from the browser.
The Facebook JavaScript API requires some configuration before it can
be used. For security purposes, web browsers limit JavaScript to mak-
ing requests only to the originating server.
Since the Facebook API needs to communicate with Facebook, we’ll
need to help it work around the browser security model. To do this,
we’ll create a file called xd_receiver.html in our public directory. The file
should have the following content:
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
Now that we have that page in place, we can create a page that uses
the Facebook API library. Let’s use our marketing page to experiment
with the library. We’ll start by including it into our page.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 167
Figure 8.2: Facebook JS API showing a user
That takes only one line of code:
To use the library, we’ll need to create an instance of the FB.ApiClient.
Its constructor takes two parameters, our application’s API key and the
server path to our xd_receiver.html file. Before we can make API calls,
we’ll need to make sure our user is authenticated to Facebook. To do
this, we’ll call the requireLogin method. If this is the first time we’re using
the API for a user, they will be redirected to a Facebook login screen
where they are prompted to enter their login information. Once they
have authenticated with Facebook, they will be sent back to our site
where execution will continue.
Since the JavaScript API requires our user to be authenticated with
Facebook, the requireLogin method requires that a callback function be
provided. This callback will be called once authentication is complete.
You’ll see extensive use of callback functions inside the API client. In
general, we want all our code that depends upon the Facebook API to be
called from this callback. The following example opens an alert dialog
box containing the Facebook ID of the logged-in user; give it a try:
// Create an ApiClient object, passing app's API key and
// a site relative URL to xd_receiver.htm
var api = new FB.ApiClient('<%=Facebooker.api_key%>' ,
'/xd_receiver.html' ,
null);
api.requireLogin(function(exception) {
alert("Current user id is " + api.get_session().uid);
});
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
A CCESSING F ACEBOOK O UTSIDE THE C ANVAS 168
I’ve had good luck getting that code to work in Firefox, but not in
Safari for the Mac. The problem seems to occur only when working
using script/server. I haven’t seen the same issues once the application
is deployed. You’ll want to keep this in mind while you are doing your
development.
We’ve seen how to get the Facebook ID of the current user. Now let’s
take a look at how to get the user’s name. We’ll use the users.getInfo API
call.2 Our code will need to make a call to this API function and pass in
three parameters: the Facebook ID of the person whose name we want,
the fields we want to retrieve, and a callback to be called when the
request completes. The following is an example. If you run that code,
the result should look like Figure 8.2, on the previous page.
api.requireLogin(function(exception) {
// Get the name of the current user
api.users_getInfo(api.get_session().uid,
["first_name" ],
function(result, exception) {
Debug.dump(result, 'Get first name of user ' );
});
});
In the previous code, we display the result of our API call using the
Debug.dump method. This method writes data to the browser’s Java-
Script console. If you would rather see the result on your web page,
you can include a text area with the ID _traceTextBox like in the following
code:
This code is very different from our Ruby code that does the same thing,
user.first_name. Along with having to deal with callback functions, we also
have to deal with an awkward naming convention. You’ll find that these
method names very closely match the ones used in the official Facebook
PHP client.
We’re not limited to such basic data retrieval. We can make much more
complicated calls. For instance, we could get the names of all a user’s
friends by nesting an API call inside our callback.
2. You can find documentation at http://wiki.developers.facebook.com/index.php/Users.getInfo.
Report erratum
Prepared exclusively for Alison Tyler this copy is (P1.0 printing, September 2008)
Xem thêm các tài liệu liên quan
- Kỹ thuật lập trình
- Sổ tây cms bảo mật
- VBA CHO POWERPOINT
- Hướng dẫn sử dụng vision 2010 - part 21
- Wifi security phần 2
- Tuyển tập 20 đề thi thử thpt quốc gia môn toán năm 2016
- Firewall policies and vpn configurations 2006 phần 2
- Hướng dẫn sử dụng vision 2010 - part 30
- Pro openssh phần 10
- Ưng dụng excel trong giải quyết các bài toán quy hoạch tuyến tính