Home  >  

Extended ProfilePic Component with Effects

Author photo
AddThis Social Bookmark Button
In this article we will extend the ProfilePic component from the last article and show example of how we can use it in real world scenarios. We will create a Facebook application that asks the user a question about the displayed friend. Yes, it is in fact a copy of an existing application (I don’t remember the name and I don’t want to make advertising). The application simply switches from one question to another and associates the displayed friend with the question. Like, for example, the question appears “Would you ever like to date Monica?" and the application user can simply select between “Yes” or “No”. On the other side, you can choose if you want the answer to be sent to the user as a notification. Personally I enjoyed the application and will use it as a reference to make a Flash version out of it. On the other side, we will use the component and add features to it so the sample will demonstrate how useful the component can be in the development process. We will have the freedom to create our own questions. They can be stored either in the database or XML, but in our case it will be stored in a flat XML file.
series badge
Find the rest of this series on the series page.
OK, now let’s move on and start with the development. First of all, we need to remember that we will use the same application like in the previous sample. We have the .fla ready, we have api key and we have the secret key and we have the ProfilePic component ready. We also have the DisplayStatus component, but in this sample, it does not have that much value so we will leave it out this time. Here is the final picture of the application:

article8_img13.png
The task of creating such an app may seem a bit overwhelming, but it's not that difficult. Not easy, but also not too difficult. Once we define our question, we will store it in an XML file. Then, our Facebook swf will load the XML. The questions will be shuffled and the array of friends will be loaded. One question will be associated with one randomly selected friend and the application user will answer with yes or no.
Open the .fla we previously worked on and remove all instances of the components from the stage.

article8_img1.jpg
Also, remove all the ActionScript we worked on. OK, for now leave the Flash IDE we need to make sure the XML file is created. Because the application will be some sort of a quiz, the XML will consist only of questions. So here is the XML structure:

<?xml version='1.0'?>
<questions>
	<question>Would you like to kiss (friend)?</question>
	<question>Would you like to live with (friend) on a lone island?</question>
	<question>Would you like to marry (friend)?</question>
</questions>
Open you favorite XML / plain text editor and paste it. You can add as many questions as you want. Just make sure they are inside the nodes. Notice the (friend) placeholder inside every question? We will use it later in the application to replace it with actual names of friends. So the question will not appear like:
Would you like to marry (friend)?
Instead it will appear like:
Would you like to marry Monica?
Beside that, the ProfilePic component will appear and display the image of the user. Yeah!
OK, save the XML file we just created in the root folder of our standard Facebook app.

article8_img2.png
Save it as “questions.xml”. Since the XML is ready to go, we can go back to the Flash IDE and handle to loading of the XML content in our swf file.
Go to the first frame and paste the following code:

import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.xml.*;
import flash.events.*;
import flash.errors.*;

var mainXML:XML;
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onComplete);
loader.load(new URLRequest("questions.xml"));

function onComplete(evt:Event){
 	
     mainXML = new XML(loader.data)
     trace("xml loaded, start working with data&#133;");
 	
}
There is really no need to explain those few lines of code a lot. First we import the classes needed for connecting to URL destinations and for working with XML.
Hold on (you might say)! Yes, there is an error here:

article8_img3.png
and it says something about security. Hmm, the XML file that is located in the same folder like the swf cannot be loaded? It’s strange, but because of the various restrictions Facebook placed on the applications, there must be a workaround to load the XML data. And the workaround is AMFPHP from the previous articles! Now we can see how cool it is when we are proficient in many areas, not only ActionScript. In order for the XML to load, we need to create a small PHP class file with a method to load XML. This method will simply be called from the swf and we will bypass the security restriction.
Let’s see how it is done. Since we know that we need to work with AMFPHP, we need to make sure that the libraries are there. Remember the articles where we worked with AMFPHP? Because we work on the same application, the AMFPHP library is still there and we do not need to set it up again. Cool, in the folder amfphp/services we should have the class “FBServices”. Remove all the methods from it and just leave the constructor. We only need to method for loading the xml and passing it to flash, so paste this method below the constructor:

function loadXML($file){
 	$myFile = "../../".$file;
 	$fh = fopen($myFile, 'r');
 	$theData = fread($fh, filesize($myFile));
 	fclose($fh);
 	return $theData;
}
Now upload the class file to the services folder (amfphp/services). Now load the class browser via http://www.myserver.com/yourAppName/amfphp/browser/.

article8_img4.png
Now click on the FBServices class and click on the loadXML method. We can see there is a text field where we can enter the parameter for the questions. So in that field we will enter “questions.xml”:

article8_img5.png
When you press questions.xml we finally see the result in the browser window:

article8_img6.png
We need to keep in mind that this is just plain text. Now our goal is to load it directly from flash. Here is how the code looks like using ActionScript:

var gateway:NetConnection = new NetConnection();
var responder:Responder = new Responder(onResult, onFault);

gateway.connect("http://www.yourserver.com/yourApp/amfphp/gateway.php");
gateway.call("FBServices.loadXML", responder, "questions.xml");
 
function onResult(responds:Object):void {
 	trace ("onResult: " + responds)
}
 
function onFault(responds:Object):void {
 	trace("fault " + responds.description);
}
When the swf is uploaded, we can see the xml in the ouput window:

article8_img7.png
Cool! We are now ready to parse the XML since we skipped the security check here. There are plenty of ways to handle XML in ActionScript but in order to do that we first need to create a separate function:

function parseXML(xmlStr:String):void{
 	
 	mainXML = new XMLDocument();
 	mainXML.ignoreWhite = true;
 	mainXML.parseXML(xmlStr);
 	
 	var xmlQ:Array = mainXML.firstChild.childNodes;
 	var i = 0;
 	var len = xmlQ.length;
 	
 	for(i = 0; i < len; i++){
  		
  		questions.push(xmlQ[i].firstChild.toString());
  		
  	}
 	
 	trace("questions: " + questions);
 	
}
Bur first, in order for the function to work, we need to define the mainXML variable and the question array. So, in order not to get confused, here is the complete code from the first frame:

import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.xml.*;
import flash.events.*;
import flash.errors.*;

var mainXML:XMLDocument = new XMLDocument();

var questions:Array = new Array();

var gateway:NetConnection = new NetConnection();
var responder:Responder = new Responder(onResult, onFault);

gateway.connect("http://www.yourserver.com/yourApp/amfphp/gateway.php");
gateway.call("FBServices.loadXML", responder, "questions.xml");
 
function onResult(responds:Object):void {
 	parseXML(responds.toString());
}
 
function onFault(responds:Object):void {
 	trace("fault " + responds.description);
}

function parseXML(xmlStr:String):void{
 	
 	mainXML = new XMLDocument();
 	mainXML.ignoreWhite = true;
 	mainXML.parseXML(xmlStr);
 	
 	var xmlQ:Array = mainXML.firstChild.childNodes;
 	var i = 0;
 	var len = xmlQ.length;
 	
 	for(i = 0; i < len; i++){
  		
  		questions.push(xmlQ[i].firstChild.toString());
  		
  	}
 	
 	trace("questions: " + questions);
 	
}
Now, when the swf is compiled and the uploaded, we can see that the array is of questions is loaded:
questions: Would you like to kiss (friend)?,Would you like to live with (friend) on a lone island?,Would you like to marry (friend)?
Cool! Until now we did a great job. We loaded the xml and parsed the xml. We stripped the unnecessary xml nodes from the strings and the result of the hard work is an array of pure questions. Now, since we have our questions ready, we have to take care of other things. Now we need to load the list of friends we have. For that, we will have the look at the 5th article. The goal here is to load the collection of friends with all the data (uid, first name, last name, etc.) and to associate it with one random question. So here is the code to load the friends. First import the fancy Facebook classes:

import com.facebook.data.users.GetInfoData;
import com.facebook.utils.FacebookSessionUtil;
import com.facebook.data.users.FacebookUser;
import com.facebook.data.users.GetInfoFieldValues;
import com.facebook.commands.users.GetInfo;
import com.facebook.commands.friends.*;
import com.facebook.data.friends.*;
import com.facebook.net.FacebookCall;
import com.facebook.events.FacebookEvent;
import com.facebook.Facebook;

<div style="text-align: left;">Then, we define the variables somewhere at the top of the code:</div>

var fbook:Facebook;
var session:FacebookSessionUtil;
var user:FacebookUser;
And then we can start the Facebook session:

session = new FacebookSessionUtil("YOUR_API_KEY", "YOUR_SECRET", loaderInfo);
fbook = session.facebook;
Now we call the friends right after the xml is loeded, so in the parseXML question, after the xml parsing, place the following code:

var call:GetFriends = new GetFriends();
call.addEventListener(FacebookEvent.COMPLETE, onFriends);
fbook.post(call);
We receive the user ids in the onFriends function:

function onFriends(e:FacebookEvent):void{
 	
 	var friendsData = (e.data as GetFriendsData);
 	var friends = friendsData.friends;
 	
 	var i;
 	var len = friends.length;
 	var uids:Array = new Array();
 		
 	for(i = 0; i < len; i++){
  			
  		var user = friends.getItemAt(i) as FacebookUser;
  		uids.push(user.uid);
  			
  	}
 		
 	var call:FacebookCall = fbook.post(new GetInfo(uids, [GetInfoFieldValues.ALL_VALUES]));
 	call.addEventListener(FacebookEvent.COMPLETE, onDataRecieve);
     fbook.post(call);
 
 	
}
Just like in the 5th article, after the user ids are loaded, we call the getInfo function to retrieve the info of all friends and receive the data in the following function:

function onDataRecieve(e:FacebookEvent){
 	
 	users = ((e.data as GetInfoData).userCollection);
 
 	trace(&#8220;users: &#8221; + users);
 	
}
This is a very important step, so in order not the get confused, here is the complete code from the first frame:

import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.xml.*;
import flash.events.*;
import flash.errors.*;
import com.facebook.data.users.GetInfoData;
import com.facebook.utils.FacebookSessionUtil;
import com.facebook.data.users.FacebookUser;
import com.facebook.data.users.GetInfoFieldValues;
import com.facebook.commands.users.GetInfo;
import com.facebook.commands.friends.*;
import com.facebook.data.friends.*;
import com.facebook.net.FacebookCall;
import com.facebook.events.FacebookEvent;
import com.facebook.Facebook;

var fbook:Facebook;
var session:FacebookSessionUtil;
var user:FacebookUser;

session = new FacebookSessionUtil("YOUR_API_KEY", "YOUR_SECRET",loaderInfo);
fbook = session.facebook;

var mainXML:XMLDocument = new XMLDocument();

var questions:Array = new Array();
var users;

var gateway:NetConnection = new NetConnection();
var responder:Responder = new Responder(onResult, onFault);

gateway.connect("http://www.yourserver.com/yourApp/amfphp/gateway.php");
gateway.call("FBServices.loadXML", responder, "questions.xml");
 
function onResult(responds:Object):void {
 	parseXML(responds.toString());
}
 
function onFault(responds:Object):void {
 	trace("fault " + responds.description);
}

function parseXML(xmlStr:String):void{
 	
 	mainXML = new XMLDocument();
 	mainXML.ignoreWhite = true;
 	mainXML.parseXML(xmlStr);
 	
 	var xmlQ:Array = mainXML.firstChild.childNodes;
 	var i = 0;
 	var len = xmlQ.length;
 	
 	for(i = 0; i < len; i++){
  		
  		questions.push(xmlQ[i].firstChild.toString());
  		
  	}
 	
 	var call:GetFriends = new GetFriends();
 	call.addEventListener(FacebookEvent.COMPLETE, onFriends);
 	fbook.post(call);
 	
}

function onFriends(e:FacebookEvent):void{
 	
 	var friendsData = (e.data as GetFriendsData);
 	var friends = friendsData.friends;
 	
 	var i;
 	var len = friends.length;
 	var uids:Array = new Array();
 		
 	for(i = 0; i < len; i++){
  			
  		var user = friends.getItemAt(i) as FacebookUser;
  		uids.push(user.uid);
  			
  	}
 		
 	var call:FacebookCall = fbook.post(new GetInfo(uids, [GetInfoFieldValues.ALL_VALUES]));
 	call.addEventListener(FacebookEvent.COMPLETE, onDataRecieve);
     fbook.post(call);
 
 	
}

function onDataRecieve(e:FacebookEvent){
 	
 	users = ((e.data as GetInfoData).userCollection);
 
 	trace("users: " + users);
 	
}
So now the next step here is somehow to associate the question with the user. Pick one user, pick one question and display it on the screen, as simple as that. But for this to happen, we will need to create a movieclip containing the profile pic, the question and the buttons. This is pretty straightforward. Go back to the Flash IDE and create a new movieclip via Insert -> New Symbol. Create the new movieclip called QuestionHolder:

article8_img8.png
Now, the Flash IDE will take you automatically to the timeline of the movieclip. Create two layers. The above one Actions the lower one Components:

article8_img9.png
Place the ProfilePicDisplay component to the stage.

article8_img10.png
Name it profilePic_mc in the property inspector. Next to that component, place a dynamic text field right beside the profile pic component and name it q_txt. Place two buttons below the text field. Label it Yes and No respectively and name them yes_btn and no_btn in the property inspector respectively.

article8_img11.png
Place the following code to the first frame of this movieclip:

import com.facebook.Facebook;

function setQuestion(uid:Number, question:String, fBook:Facebook){
 	profilePic_mc.load(fBook, uid);
 	q_txt.text = question;
}
Now go back to the main timeline and paste the following function below all code:

function setQuestion(){
 	
 	var randUserNum = Math.round(Math.random() * users.length);
 	var uid = (users.getItemAt(randUserNum)).uid;
 	var randQNum = Math.round(Math.random() * questions.length);
 	var question = questions[randQNum];
 	qHolder_mc.setQuestion(uid, question, fbook);
 	
}
This function needs to be called as soon as the friends and questions are loaded:

function onDataRecieve(e:FacebookEvent){
 	users = ((e.data as GetInfoData).userCollection);
 	setQuestion();
}
Now compile the swf and upload it to the server. When we call the app in Facebook, we see something similar to this:

article8_img12.png
Cool, still there is something missing, the (friend) placeholder is still there and needs to be replaced. Add the function below in the main timeline:

function replacePlaceHolder(str:String, name:String){
 	var index:Number = str.indexOf("(friend)");
 	var firstChunk = str.substr(0, index - 1);
 	var secondChunk = str.substr(index + 9, str.length);
 	var sentence = firstChunk + " " + name + " " + secondChunk;
 	return sentence;
}
And the setQuestion function needs a slight modification:

function setQuestion(){
 	
 	var randUserNum = Math.round(Math.random() * users.length);
 	var uid = (users.getItemAt(randUserNum)).uid;
 	var firstName = (users.getItemAt(randUserNum)).first_name;
 	var randQNum = Math.round(Math.random() * questions.length);
 	var question = replacePlaceHolder(questions[randQNum], firstName);
 	qHolder_mc.setQuestion(uid, question, fbook);
 	
}
This is cool! Now when we run this sample in the Facebook canvas, we see the actual name of the friend in the question:

article8_img13.png
The only thing that is left here to do is to modify to make the buttons work. Here how it is done. Go to the QuestionHolder movieclip and place the following actions in the timeline:

yes_btn.addEventListener(MouseEvent.CLICK, yesClicked);
no_btn.addEventListener(MouseEvent.CLICK, noClicked);

function yesClicked(e:MouseEvent):void{
 	dispatchEvent(new Event("yesClicked"));
}

function noClicked(e:MouseEvent):void{
 	dispatchEvent(new Event("noClicked"));
}
Go back to the main timeline and place this script at the bottom:

qHolder_mc.addEventListener("yesClicked", yesClicked);
qHolder_mc.addEventListener("noClicked", noClicked);

function yesClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}

function noClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}
Now when the swf is compiled and uploaded a new friend / question pair appears.
Here is the complete code from the main timeline:

import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.xml.*;
import flash.events.*;
import flash.errors.*;
import com.facebook.data.users.GetInfoData;
import com.facebook.utils.FacebookSessionUtil;
import com.facebook.data.users.FacebookUser;
import com.facebook.data.users.GetInfoFieldValues;
import com.facebook.commands.users.GetInfo;
import com.facebook.commands.friends.*;
import com.facebook.data.friends.*;
import com.facebook.net.FacebookCall;
import com.facebook.events.FacebookEvent;
import com.facebook.Facebook;

var fbook:Facebook;
var session:FacebookSessionUtil;
var user:FacebookUser;

session = new FacebookSessionUtil("YOUR_API_KEY", "YOUR_SECRET",loaderInfo);
fbook = session.facebook;

var mainXML:XMLDocument = new XMLDocument();

var questions:Array = new Array();
var users;

var gateway:NetConnection = new NetConnection();
var responder:Responder = new Responder(onResult, onFault);

gateway.connect("http://www.yourserver.com/yourApp/amfphp/gateway.php");
gateway.call("FBServices.loadXML", responder, "questions.xml");
 
function onResult(responds:Object):void {
 	parseXML(responds.toString());
}
 
function onFault(responds:Object):void {
 	trace("fault " + responds.description);
}

function parseXML(xmlStr:String):void{
 	
 	mainXML = new XMLDocument();
 	mainXML.ignoreWhite = true;
 	mainXML.parseXML(xmlStr);
 	
 	var xmlQ:Array = mainXML.firstChild.childNodes;
 	var i = 0;
 	var len = xmlQ.length;
 	
 	for(i = 0; i < len; i++){
  		
  		questions.push(xmlQ[i].firstChild.toString());
  		
  	}
 	
 	var call:GetFriends = new GetFriends();
 	call.addEventListener(FacebookEvent.COMPLETE, onFriends);
 	fbook.post(call);
 	
}

function onFriends(e:FacebookEvent):void{
 	var friendsData = (e.data as GetFriendsData);
 	var friends = friendsData.friends;
 	var i;
 	var len = friends.length;
 	var uids:Array = new Array();
 	for(i = 0; i < len; i++){
  		var user = friends.getItemAt(i) as FacebookUser;
  		uids.push(user.uid);
  	}
 	
 	var call:FacebookCall = fbook.post(new GetInfo(uids, [GetInfoFieldValues.ALL_VALUES]));
 	call.addEventListener(FacebookEvent.COMPLETE, onDataRecieve);
     fbook.post(call);
 	
}

function onDataRecieve(e:FacebookEvent){
 	users = ((e.data as GetInfoData).userCollection);
 	setQuestion();
}

function setQuestion(){
 	
 	var randUserNum = Math.round(Math.random() * users.length) - 1;
 	var uid = (users.getItemAt(randUserNum)).uid;
 	var firstName = (users.getItemAt(randUserNum)).first_name;
 	var randQNum = Math.round(Math.random() * questions.length) - 1;
 	var question = replacePlaceHolder(questions[randQNum], firstName);
 	qHolder_mc.setQuestion(uid, question, fbook);
 	
}

function replacePlaceHolder(str:String, name:String){
 	var index:Number = str.indexOf("(friend)");
 	var firstChunk = str.substr(0, index - 1);
 	var secondChunk = str.substr(index + 9, str.length);
 	var sentence = firstChunk + " " + name + " " + secondChunk;
 	return sentence;
}

qHolder_mc.addEventListener("yesClicked", yesClicked);
qHolder_mc.addEventListener("noClicked", noClicked);

function yesClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}

function noClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}

<div style="text-align: left;">And here is the code from the QuestionHolder first frame:</div>

import com.facebook.Facebook;

function setQuestion(uid:Number, question:String, fBook:Facebook){
 	profilePic_mc.load(fBook, uid);
 	q_txt.text = question;
}

yes_btn.addEventListener(MouseEvent.CLICK, yesClicked);
no_btn.addEventListener(MouseEvent.CLICK, noClicked);

function yesClicked(e:MouseEvent):void{
 	dispatchEvent(new Event("yesClicked"));
}

function noClicked(e:MouseEvent):void{
 	dispatchEvent(new Event("noClicked"));
}
Now you might ask, where are the effects the article is talking about? Well, I do not want to make it to boring. As a readers exercise, opet the ProfilePicDisplay class file and try to add various effects. Also, we talked about that we want to notify users of the answers. You can do that in both functions:

function yesClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}

function noClicked(e:Event):void{
 	//do something before moving on...
 	setQuestion();
}
Try to use the notifications command classes and you will get the desired result.
I hope you enjoyed the article, I really did!


To view the entire series please visit http://www.insideria.com/series-facebook-dev.html

Read more from Mirza Hatipovic. Mirza Hatipovic's Atom feed mirzahat on Twitter

Comments

2 Comments

tykebloke said:

hi. very helpful series. can you point me to any info on publishing images from a flash application to the profile pic or wall? I can find very little information on the web for this, and i see this as the rossetta stone for flash developers wanting to create facebook apps via actionscript 3.

thanks

Mirza Hatipovic said:

you can publish a full sized story to the profile...:-) check my article
on publishing news...it covers short sized stores, but you cehck
experiment

Leave a comment


Tag Cloud

iPad

What's your take on the iPad? (Putting aside the Flash/iPad flame war)

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.