Advertisement

Create a Facebook Graph API App in Flash: Generating Embed Code

by

Welcome back to our Facebook Flash application tutorial! In this part, we're going to continue building the Flash interface for building the Facebook slider. The interface will handle choosing an album for displaying in a Facebook tab.


Also Available in This Series:

  1. Create a Facebook Graph API App in Flash: Setup and Design
  2. Create a Facebook Graph API App in Flash: Generating Embed Code
  3. Create a Facebook Graph API App in Flash: Loading Events
  4. Create a Facebook Graph API App in Flash: Displaying Facebook Info

Final Result Preview

This application allows you to select a Facebook album or event list from one of your public pages and turn it into a slideshow for your page tab. When the users enter your page, they will see photos from your chosen album, with your photo title and description, or event name, date and invites ( for the event tab ).

You'll need to be logged in to Facebook in order to see this demo: https://apps.facebook.com/activetuts_tabmaker/




Step 1: Where We Left Off

In the first part of this series, we had just finished loading the pages and we displayed them for the sake of the tutorial. If you notice, Facebook returns all pages together with all the apps the account has ( the API endpoint is /me/accounts anyway ). We need to show just the pages.

The problem is the category of a returned item has multiple values. What we'll do here is check whether the category is not "Application" so we'll get all the pages the account has. For this we'll rewrite the accounts_loaded() function:

 
private function accounts_loaded( succ, fail ){ 
	if( succ ){ 
		rs = new Array(); 
		var i=succ.length; 
		if( i > 0 ){ 
			while( i-- ){ 
				if( succ[i].category !== 'Application' ){ 
					rs.push( succ[i] ); 
				} 
			} 
		} 
		offset = 0; 
		show_pages(); 
	} else { 
		create_heading('An error occured loading your pages'); 
	} 
}

We're using the rs variable to hold the returned items. We are filling the array only with the pages, excluding applications. I also check whether the succ object is not empty, in which case I call create_heading(), which we'll write in a moments. I have used a while loop to count down from the biggest index to 0, because it is faster, and once the index equals 0 it exits the loop anyway.


Step 2: create_heading() Function

Let's create the create_heading() function, which will use throughout the app to tell the user where he is:

 
private function create_heading( txt ){ 
	if( heading ){ 
		removeChild( heading ); 
	} 
	heading = new TextField(); 
	heading.defaultTextFormat = new TextFormat('Helvetica',14,0xffffff,true ); 
	heading.text = txt; 
	heading.selectable = false; 
	heading.autoSize = 'left'; 
	heading.x = 20; 
	heading.y = 100; 
	addChild( heading ); 
}

We use the heading variable to set up a text field and position it at 100px from the top. We also check if the heading already exists, in which case we remove it. This way, you can call this function multiple times and every time the heading will change.


Step 3: Refactoring the Constructor

We need to set up a couple of things before we delve into the paging of the results and showing the pages.

First, we need to create a container sprite in which we'll hold the page items. Second, we need to setup the next and previous buttons, as they will stay troughout all the states of the app.

Modify the constructor function to look like this:

 
public function AlbumMaker( m ){ 
	main = m; 
	main.debug('AlbumMaker instanced'); 
			 
	state = 1; 
	create_heading('Select a page'); 
			 
	//get the accounts 
	Facebook.api('me/accounts', accounts_loaded, {limit: 100} ); 
 
	container = new Sprite(); 
	container.y = 120; 
	addChild( container ); 
			 
	next_pager = new NextButton(); 
	next_pager.addEventListener( MouseEvent.CLICK, pager_next ); 
	prev_pager = new PrevButton(); 
	prev_pager.addEventListener( MouseEvent.CLICK, pager_prev ); 
	prev_pager.x = 40; 
	prev_pager.y = next_pager.y = 450; 
	next_pager.x = 200; 
	next_pager.visible = prev_pager.visible = false; 
	addChild( next_pager ); 
	addChild( prev_pager ); 
}

I am setting the state variable to 1, and creating the container sprite and positioning it at 120px from the top. Next, I am adding the NextButton and PrevButton buttons which we'll create in a moment, and setting their location to be one beside the other. I'm also adding click handlers to the buttons, which we'll create later.


Step 4: Create the NextButton and PrevButton

Go back to the TabMaker_app.fla and, in the Library, duplicate the album_btn twice, making a NextButton and a PrevButton symbol, respectively. Give them the respective class names and modify their text field labels like this:



Step 5: The show_pages() Function

This is where the pages are displayed ( and paginated, where appropriate ):

 
private function show_pages(){ 
	var ypos = 20; 
    if( rs.length > 0 ){ 
		for( var i=offset; i< (offset+PAGE_ROWS); i++ ){ 
			if( rs[ i ] ){ 
				var p = new PageItem( rs[ i ], main ); 
				p.addEventListener( MouseEvent.CLICK, load_albums ); 
				container.addChild( p ); 
				p.y = ypos; 
				ypos += (p.height+20); 
						 
			} 
		} 
		show_pager(); 
	} else { 
		create_heading('You have no pages created.'); 
	}			 
}

There's a lot happening in this function, so let me explain it bit by bit:

First, I set the ypos variable to 20. The ypos variable is used to maintain the y position of the movie, in order to create a list.

Next, if the rs.length() is 0, I announce the user he has no pages created - in which case, he can't even use the app, but that's another thing!

The for() loop is where the whole action takes place: I set the i variable to the offset, which at first is 0. Later, when the user clicks on the next button, we can increase the offset and the for loop will start at a different index.

The second condition in the for loop is checking whether i is smaller than offset+PAGE_ROWS. PAGE_ROWS holds the number of items displayed at a time. This way, we can paginate the array. Inside the loop, I check if the index actually exists. We need this in case the index is out of bounds. How can it be ? Let me explain:

Let's say we have PAGE_ROWS = 10 items and we have 15 items returned. The first time, we'll get the first 10 items, while after we paginate the array, offset will be 10, so after the last index 14, index 15 (the 16th item) will not exist, in which case we will get an out of bounds error.

You can make the for loop faster by stopping the unnecesary loops with break() in an else statement:

 
for( var i=offset; i< (offset+PAGE_ROWS); i++ ){ 
	if( rs[ i ] ){ 
		var p = new PageItem( rs[ i ], main ); 
		p.addEventListener( MouseEvent.CLICK, load_albums ); 
		container.addChild( p ); 
		p.y = ypos; 
		ypos += (p.height+20); 
				 
	} else { 
    	break(); 
    } 
}

Since this tutorial is showing you only the idea, I won't do this in the next parts, but you can often make the code faster using this small enhacement. The rest of the code is pretty easy; I create a PageItem class ( which we'll make in a moment ) and place instances one after the other.


Step 6: PageItem Class

We need to design a movieclip to show the page. Create a 660x80px rectangle with the style like in the picture, and press F8 to turn it into a movieclip with a linkage name of tabmaker.PageItem:



Create two text fields; set the type as TLFText, unselectable; give them a white color and the instance name title_txt and categ_txt, respectively. (I have also added a white rectangle where the thumb will be, but this is not necessary, as we will create the thumb at runtime.)



Step 7: PageItem Class

In the tabmaker folder, create a new file, PageItem.as, and enter the following:

 
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.net.*; 
	import flash.text.*; 
	import flash.external.ExternalInterface; 
	import flash.desktop.*; 
	import fl.motion.Color; 
	 
	import com.greensock.*; 
	import com.greensock.easing.*; 
	 
    public class PageItem extends MovieClip { 
             
        private var cfg; 
        private var main; 
        private var ct; 
        private var thumb; 
        private var msk; 
             
        public function PageItem( cfg, main ) { 
            this.cfg = cfg; 
            this.main = main; 
            this.addEventListener( Event.ADDED_TO_STAGE, init ); 
                 
            ct = new Color(); 
            ct.tintColor = 0xFFFFFF; 
            ct.tintMultiplier = 0.1; 
        } 
             
        private function init( e ){ 
            title_txt.text = cfg.name; 
            categ_txt.text = cfg.category; 
            mouseChildren = false; 
            buttonMode = true; 
                 
            this.addEventListener( MouseEvent.MOUSE_OVER, on_over ); 
            this.addEventListener( MouseEvent.MOUSE_OUT, on_out ); 
            var tl = new Loader(); 
            tl.contentLoaderInfo.addEventListener( Event.COMPLETE, on_loaded_thumb ); 
            tl.load( new URLRequest( main.get_facebook_image( cfg.id ) ) ); 
        } 
    } 
     
}

Let's see what happens in this class. The class has some variables:

  • First, cfg, which will hold the JSON object that we pass from the page loop.
  • We also hold a reference to the main TabMaker class with the use of the main variable.
  • There's ct, a color transform object we will use to make a subtle effect on the thumbnail.
  • Also, thumb, which will hold the thumbnail.
  • Finally, there's mask, which is a sprite we will use to mask the image.

The constructor function accepts two arguments: the config object and the TabMaker class.

Now, I have saved the two parameters for later use, since when the constructor is executed the sprite may not be on the stage, in which case we cannot access the two textfields created in the Flash IDE. I am listening for the Event.ADDED_TO_STAGE, and in the init function I can pass the text to the two textfields correctly. If you don't do this you get an error when Flash Player runs which basically says it can't find the two textfields.

Next, I enable buttonMode on the sprite, which creates the hand cursor when you hover over it, and I set mouseChildren to false. If you don't do this, your hand cursor will turn into a text cursor when you hover over the textfields. Using mouseChildren allows you to tell the sprite to ignore the sprite's children events.

Then, I create a Loader, create an Event.COMPLETE for when the image has downloaded, and load the image - however, you'll see I call main.get_facebook_image(). What is this, you ask ? Well, in order to explain, I you need to understand how the Facebook Graph works.


Step 8: A Little Theory

Every picture in Facebook is tied to an object in the graph. We have the id of the page item, so we can call /(id)/picture with an access token, and if the picture is public or is not restricted to the current user, we get the photo.

The Facebook SDK already has this functionality coded, so we can use Facebook.getImageUrl() to fetch the picture. The code for get_facebook_image() has to go in the original TabMaker.as file and looks like this:

 
public function get_facebook_image( id ){ 
	return Facebook.getImageUrl( id, 'normal'); 
}

The getImageUrl() accepts an id and a size, which can be "normal", "large" or "original". I could have used the Facebook object directly, but this way we have all the image loading function in a single place. If for some reason, the Facebook guys change a bit of the API ( trust me, this happens a lot - it happened while I was testing the app ), we can just modify the get_facebook_image() function, without modifying the SDK.


Step 9: Time for Fun

We need to create the thumb, resize the photo and mask it. Let's do this now.

Go back to the PageItem.as file and enter the following:

 
public function on_loaded_thumb( e ){			 
    thumb = e.target.loader; 
    var ratio = 0; 
    var th = 70; 
    var tw = 90; 
    if( thumb.width > thumb.height ){ 
        ratio = thumb.width / thumb.height; 
        thumb.width = th*ratio; 
        thumb.height = th; 
    } else { 
        ratio = thumb.height / thumb.width; 
        thumb.width = tw; 
        thumb.height = tw*ratio; 
    } 
    thumb.x = 55; 
    thumb.y = 5; 
     
    msk = new Shape(); 
    msk.graphics.beginFill(0xffffff,1); 
    msk.graphics.drawRect(54,4,tw,th ); 
    msk.graphics.endFill(); 
     
    addChild( msk ); 
    addChild( thumb ); 
    thumb.mask = msk; 
    thumb.alpha = 0; 
    TweenLite.to( thumb, 0.3, { alpha: 1 } ); 
}

Let me go quickly through the whole code, as our journey is still a long way from the end.

First I set the thumb variable to the event's target.loader, which is the bitmap. (Of course, for best results, you should resample the bitmapData and create a bitmap with the size, but for the purpose of a thumb it's not worth it.)

Next, I create ratio, th and tw variables to recalculate and store the new size. If the width is bigger than the height, we have a landscape photo, otherwise we have a portrait one, so we update the width and height accordingly.

After that, I set the x and y coordinates. The x is 55 because we are adding the page item at (0,0) and the graphic background starts at 50px from the margin.

The next lines are just the creation of the mask, using a Shape object. For masks, it's best to use a Shape, as the Shape class is smaller in terms of memory than Sprite ( and it doesn't have any interactivity ).

Lastly, I use a small TweenLite fade to fade the thumbnail.


Step 10: Hover Functions

There are also two more functions, on_over() and on_out(), which are very simple, basically just applying the color transform for creating the hover effect:

 
private function on_over( e ){ 
    this.transform.colorTransform = ct; 
} 
 
private function on_out( e ){ 
    this.transform.colorTransform = new Color(); 
}

This is the final code for the PageItem.as file:

 
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.net.*; 
	import flash.text.*; 
	import flash.external.ExternalInterface; 
	import flash.desktop.*; 
	import fl.motion.Color; 
	 
	import com.greensock.*; 
	import com.greensock.easing.*; 
	 
	public class PageItem extends MovieClip { 
		 
		private var cfg; 
		private var main; 
		private var ct; 
		private var thumb; 
		private var msk; 
		 
		public function PageItem( cfg, main ) { 
			this.cfg = cfg; 
			this.main = main; 
			this.addEventListener( Event.ADDED_TO_STAGE, init ); 
			 
			ct = new Color(); 
			ct.tintColor = 0xFFFFFF; 
			ct.tintMultiplier = 0.1; 
		} 
		 
		private function init( e ){ 
			title_txt.text = cfg.name; 
			categ_txt.text = cfg.category; 
			mouseChildren = false; 
			buttonMode = true; 
			 
			this.addEventListener( MouseEvent.MOUSE_OVER, on_over ); 
			this.addEventListener( MouseEvent.MOUSE_OUT, on_out ); 
			var tl = new Loader(); 
			tl.contentLoaderInfo.addEventListener( Event.COMPLETE, on_loaded_thumb ); 
			//main.debug( main.get_facebook_image( cfg.id ) ); 
			tl.load( new URLRequest( main.get_facebook_image( cfg.id ) ) ); 
		} 
		 
		public function on_loaded_thumb( e ){			 
			thumb = e.target.loader; 
			var ratio = 0; 
			var th = 70; 
			var tw = 90; 
			if( thumb.width > thumb.height ){ 
				ratio = thumb.width / thumb.height; 
				thumb.width = th*ratio; 
				thumb.height = th; 
			} else { 
				ratio = thumb.height / thumb.width; 
				thumb.width = tw; 
				thumb.height = tw*ratio; 
			} 
			thumb.x = 55; 
			thumb.y = 5; 
			 
			msk = new Shape(); 
			msk.graphics.beginFill(0xffffff,1); 
			msk.graphics.drawRect(54,4,tw,th ); 
			msk.graphics.endFill(); 
			 
			addChild( msk ); 
			addChild( thumb ); 
			thumb.mask = msk; 
			thumb.alpha = 0; 
			TweenLite.to( thumb, 0.3, { alpha: 1 } ); 
		} 
		 
		private function on_over( e ){ 
			this.transform.colorTransform = ct; 
		} 
		 
		private function on_out( e ){ 
			this.transform.colorTransform = new Color(); 
		} 
		 
	} 
	 
}

Step 11: Back to the AlbumMaker

Let's begin making the navigation for next and previous pages:

 
private function pager_next( e ){ 
    clear_pages(); 
    if( state == 1 ){ 
        offset += PAGE_ROWS; 
        show_pages(); 
    } else if( state == 2 ){ 
        offset += ALBUMS; 
        show_albums(); 
    } else { 
        offset += PHOTOS; 
        show_photos(); 
    } 
     
} 
private function pager_prev( e ){ 
    clear_pages(); 
    if( state == 1 ){ 
        offset -= PAGE_ROWS; 
        offset = (offset<0)? 0 : offset; 
        show_pages(); 
    } else if( state == 2 ){ 
        offset -= ALBUMS; 
        offset = (offset<0)? 0 : offset; 
        show_albums(); 
    } else { 
        offset -= PHOTOS; 
        offset = (offset<0)? 0 : offset; 
        show_photos(); 
    } 
} 
 
private function clear_pages(){ 
    while( container.numChildren ){ 
        container.removeChildAt( 0 ); 
    } 
}

The pager_next() and pager_prev() functions are called by the navigation buttons, on click.

First I call the clear_pages() function

, which basically removes all the child objects from the container sprite.

Next, I check the state variable ( as we will use the same buttons for all the states of the app ) and if the state is 1 ( we're loading the pages ), I increment or decrement the offset variable with the PAGE_ROWS variable, which I have defined at the begining of the class to 3. I also check if the offset is smaller than 0 in the pager_prev() functions. I don't check the offset in the pager_next() function, as we will hide the next button anyway if we are at the end of the array.

Once the button is clicked, we are also calling show_pages() again, but now the offset is different, so we get the next results.


Step 12: The show_pager() Function

This function shows or hides the next and prev buttons appropriately:

 
	private function show_pager(){ 
        var nrows = 0; 
        if( state == 1 ){ 
            nrows = PAGE_ROWS; 
        } else if( state == 2 ){ 
            nrows = ALBUMS; 
        } else { 
            nrows = PHOTOS; 
        } 
        main.debug( offset+nrows ); 
 
        next_pager.visible = Boolean(rs[offset + nrows]); 
        prev_pager.visible = Boolean(offset > 0); 
    }

First, I define a variable nrows.

Next, I check the state again and set the nrows to the PAGE_ROWS. I needed the nrows variable, as we have different values according to the state. I use Boolean() to set the button's visibility to true or false.

If the rs array has a key that equals offset+nrows, we can go to the next page, otherwise we'll get false, as the array will be out of bounds, which will return false, thus hiding the next_pager. I use the same technique to check if the offset is bigger than 0 to hide the prev_pager

If you want to test the movie at this point, you'll have to create empty show_albums() and show_photos() functions, otherwise you'll get an error. Once you've done that, you can see that the paging works.


Step 13: The load_albums() Function

Let's go on. When the user clicks a page, he needs to select an album on that page. We can request the graph API endpoint /id/albums which returns a collection of albums the page has.

 
	public function load_albums( e ){ 
        state = 2; 
        main.debug('Loading albums'); 
         
        page_id = rs[ offset+container.getChildIndex( e.target ) ].id; 
         
        clear_pages(); 
        create_heading('Select an album:'); 
         
        Facebook.api( '/'+page_id+'/albums', albums_loaded, {limit: 100} ); 
    }

In this function, I set the state of the app to 2, then I get the page id from the clicked button. I use the getChildIndex( e.target ) function to get the clicked index and add the offset variable to get the array key, saving the id in the page_id variable. This variable is defined in the app, so it will hold this page id.

I call clear_pages() again, change the heading and call the Facebook.api with the page_id variable.


Step 13: The albums_loaded() Function

Here's the albums_loaded() function, which resembles the accounts_loaded() function:

   
    private function albums_loaded( succ, fail ){ 
        if( succ ){ 
            rs = new Array(); 
            var i=succ.length; 
            if( i > 0 ){ 
                while( i-- ){ 
                    rs.push( succ[i] ); 
                } 
                offset = 0; 
                show_albums(); 
            } else { 
                main.show_error('You have no albums on this page.'); 
            } 
             
        } else { 
            main.show_error('An error occured loading your pages'); 
        }			 
    }

I check that the returned success object is not false, and then I recreate the rs array with the album items, set the offset to 0, and call show_albums().

If the array length is 0, this means that the page contains no albums, so we alert the user that he has no albums. I do the same if the API does not return a success object.

Step 14: Showing the Albums

Next, we call a function similar to show_pages(), the function show_albums():

   
private function show_albums(){ 
    var ypos = 20; 
    var xpos = 0; 
    for( var i=offset; i< (offset+ALBUMS ); i++ ){ 
        if( rs[ i ] ){ 
            var p = new AlbumItem( rs[ i ], main ); 
            container.addChild( p ); 
            p.y = ypos; 
            if(i%2){ 
                p.x = 390; 
                ypos += (p.height+20); 
            } else { 
                p.x = 40; 
            } 
            p.addEventListener( MouseEvent.CLICK, load_photos ); 
        } 
    } 
    show_pager(); 
}

This function is a little different from the show_pages() function, as I put two albums on a row. I'm checking for i%2 ( modulo of 2 ), which will return 0 or 1.

If the remainder is 1, I set the x to 390, and update the y accordingly; otherwise I just set the x to 40. This will arrange the albums in a grid of two columns.


Step 15: AlbumItem

Now, we need to create the actual AlbumItem class and symbol, but I will just show it here, as it is really similar to the PageItem class, only with different parameters. First, duplicate the PageItem movieclip from the fla file and call it AlbumItem, and make the following modifications:


Basically, I have modified the width to 320px, made the thumbnail square and renamed the categ_txt field to desc_txt. Set the class name in the linkage properties panel to AlbumItem, and create the AlbumItem.as file in the tabmaker folder:

   
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.net.*; 
	import flash.text.*; 
	import flash.external.ExternalInterface; 
	import flash.desktop.*; 
	import fl.motion.Color; 
	 
	import com.greensock.*; 
	import com.greensock.easing.*; 
	 
	public class AlbumItem extends MovieClip { 
		 
		private var cfg; 
		private var main; 
		private var ct; 
		private var thumb; 
		private var msk; 
		 
		public function AlbumItem( cfg, main ) { 
			this.cfg = cfg; 
			this.main = main; 
			this.addEventListener( Event.ADDED_TO_STAGE, init ); 
			 
			ct = new Color(); 
			ct.tintColor = 0xFFFFFF; 
			ct.tintMultiplier = 0.1; 
		} 
		 
		private function init( e ){ 
			title_txt.text = cfg.name; 
			desc_txt.text = cfg.count+' photos'; 
			mouseChildren = false; 
			buttonMode = true; 
			 
			this.addEventListener( MouseEvent.MOUSE_OVER, on_over ); 
			this.addEventListener( MouseEvent.MOUSE_OUT, on_out ); 
			var tl = new Loader(); 
			tl.contentLoaderInfo.addEventListener( Event.COMPLETE, on_loaded_thumb ); 
			//main.debug( main.get_facebook_image( cfg.id ) ); 
			tl.load( new URLRequest( main.get_facebook_image( cfg.cover_photo ) ) ); 
		} 
		 
		public function on_loaded_thumb( e ){			 
			thumb = e.target.loader; 
			var ratio = 0; 
			var th = 90; 
			var tw = 90; 
			if( thumb.width > thumb.height ){ 
				ratio = thumb.width / thumb.height; 
				thumb.width = th*ratio; 
				thumb.height = th; 
			} else { 
				ratio = thumb.height / thumb.width; 
				thumb.width = tw; 
				thumb.height = tw*ratio; 
			} 
			thumb.x = 5; 
			thumb.y = 5; 
			 
			msk = new Shape(); 
			msk.graphics.beginFill(0xffffff,1); 
			msk.graphics.drawRect(5,5,tw,th ); 
			msk.graphics.endFill(); 
			 
			addChild( msk ); 
			addChild( thumb ); 
			thumb.mask = msk; 
			thumb.alpha = 0; 
			TweenLite.to( thumb, 0.3, { alpha: 1 } ); 
		} 
		 
		private function on_over( e ){ 
			this.transform.colorTransform = ct; 
		} 
		 
		private function on_out( e ){ 
			this.transform.colorTransform = new Color(); 
		} 
		 
	} 
	 
}

You can see we have the same functionality; the only thing that differs is the desc_txt field, which I have modified. Obviously, I couldn't have used the same PageItem class, as we have different assets, and the thumbnail sizes vary, but you can copy the code from PageItem, replace the categ_txt with desc_txt and alter the thumbnail sizes as appropriate, and everything should work fine.


Step 16: Showing the Photos

We are nearing the final part of the application. Once the user clicks on an album, we can find out the album id in the Facebook graph. We're also going to show him the photos from that album and add some options which will use in the slider.

Let's create the load_photos() function:

   
public function load_photos( e ){ 
    state = 3; 
    main.debug('Loading photos'); 
     
    album_id = rs[ offset+container.getChildIndex( e.target ) ].id; 
     
    clear_pages(); 
    create_heading('Photos in this album:'); 
     
    Facebook.api( '/'+album_id+'/photos', photos_loaded, {limit: 200} ); 
}

Just as in load_albums(), I set the state to 3, determine the album_id variable based on the child index of the clicked album, and clear the items in the container.

Next, I change the heading and call the Facebook.api() with the album's id. In the Facebook Graph, everything has an id, so the album has an id. Looking at the Graph API Explorer, if we enter an album id, we get the info about the album. Requesting /photos on an album id will return all the photos from that album.


Step 17: The photos_loaded() Function

This function is the same as albums_loaded(), it fills the rs array with the photos, only here we do some other things as well:

   
private function photos_loaded( succ, fail ){ 
    if( succ ){ 
        rs = new Array(); 
        var i=succ.length; 
        if( i > 0 ){ 
            while( i-- ){ 
                rs.push( succ[i] ); 
            } 
            offset = 0; 
             
            show_photos(); 
            show_codebox(); 
            show_instructions(); 
             
        } else { 
            main.show_error('You have no photos in this album.'); 
        } 
         
    } else { 
        main.show_error('An error occured loading your pages'); 
    }			 
}

In addition to calling show_photos(), we also call two other functions which will show the embed code and the instructions for adding the tab.


Step 18: Showing the Photos

Let's see the show_photos() function, which by now you will notice resembles the other show functions:

   
private function show_photos(){ 
    var ypos = 20; 
    var xpos = 40; 
    for( var i=offset; i< (offset+PHOTOS ); i++ ){ 
        if( rs[ i ] ){ 
            main.debug( rs[i] ); 
            var p = new PhotoItem( rs[i], main ); 
            container.addChild( p ); 
            p.y = ypos; 
            p.x = xpos; 
            xpos += 100; 
            if( xpos > 280 ){ 
                xpos = 40; 
                ypos += 100; 
            } 
        } 
    } 
    show_pager(); 
}

This time, when we add a photo, we have a different check. If the xpos is higher than 280, we reset the xpos position and add 100 to the ypos, creating a 4 x 3 grid of photos.

Now we need to create the PhotoItem class, just like we did with the AlbumItem and PageItem.


Step 19: PhotoItem Class

Create the PhotoItem.as file in the tabmaker folder and enter the following code:

   
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.net.*; 
	import flash.text.*; 
	import flash.external.ExternalInterface; 
	import flash.desktop.*; 
	import fl.motion.Color; 
	 
	import com.greensock.*; 
	import com.greensock.easing.*; 
	 
	public class PhotoItem extends MovieClip { 
		 
		private var cfg; 
		private var main; 
		private var ct; 
		private var thumb; 
		private var msk; 
		 
		public function PhotoItem( cfg, main ) { 
			this.cfg = cfg; 
			this.main = main; 
			this.addEventListener( Event.ADDED_TO_STAGE, init ); 
			 
			ct = new Color(); 
			ct.tintColor = 0xFFFFFF; 
			ct.tintMultiplier = 0.1; 
			 
			var white_bg = new Shape(); 
			white_bg.graphics.beginFill(0xffffff,1); 
			white_bg.graphics.drawRect(0,0,80,80 ); 
			white_bg.graphics.endFill(); 
			addChild( white_bg ); 
		} 
		 
		private function init( e ){ 
			mouseChildren = false; 
			buttonMode = true; 
			 
			this.addEventListener( MouseEvent.MOUSE_OVER, on_over ); 
			this.addEventListener( MouseEvent.MOUSE_OUT, on_out ); 
			var tl = new Loader(); 
			tl.contentLoaderInfo.addEventListener( Event.COMPLETE, on_loaded_thumb ); 
			//main.debug( main.get_facebook_image( cfg.id ) ); 
			tl.load( new URLRequest( main.get_facebook_image( cfg.id ) ) ); 
		} 
		 
		public function on_loaded_thumb( e ){			 
			thumb = e.target.loader; 
			var ratio = 0; 
			var th = 76; 
			var tw = 76; 
			if( thumb.width > thumb.height ){ 
				ratio = thumb.width / thumb.height; 
				thumb.width = th*ratio; 
				thumb.height = th; 
			} else { 
				ratio = thumb.height / thumb.width; 
				thumb.width = tw; 
				thumb.height = tw*ratio; 
			} 
			thumb.x = 2; 
			thumb.y = 2; 
			 
			msk = new Shape(); 
			msk.graphics.beginFill(0xffffff,1); 
			msk.graphics.drawRect(2,2,tw,th ); 
			msk.graphics.endFill(); 
			 
			addChild( msk ); 
			addChild( thumb ); 
			thumb.mask = msk; 
			thumb.alpha = 0; 
			TweenLite.to( thumb, 0.3, { alpha: 1 } ); 
		} 
		 
		private function on_over( e ){ 
			this.transform.colorTransform = ct; 
		} 
		 
		private function on_out( e ){ 
			this.transform.colorTransform = new Color(); 
		} 
		 
	} 
	 
}

We almost have the same functionality as in AlbumItem, with a few differences: the sprite doesn't use any pre-made interface in Flash; we have different thumbnail sizes; and there's another sprite, the white_bg sprite. I used this to create a frame around the photo.

The rest of the code has the same functionality as the AlbumItem, resizing the photo and showing it with a fade in by means of the TweenLite class.


Step 20: The Code Box

We need to construct the code box, where all the embed code will be.

Create a 320x80px rectangle with the same color and a gray border as the PhotoItem and hit F8 to turn it into a MovieClip. Set the class name to CodeBox. Inside the movie clip, create a text field and a button (which I created by duplicating the album_btn), like in the picture:


Let's see the show_codebox() function:

   
private function show_codebox(){ 
    cbox = new CodeBox( EMBED_CODE, album_id ); 
    cbox.x = 380; 
    cbox.y = 140; 
    addChild( cbox ); 
     
    show_titles = new CheckBox(0, 'Show photo titles'); 
    show_titles.x = 380; 
    show_titles.y = 110; 
    addChild( show_titles ); 
    show_titles.addEventListener( MouseEvent.CLICK, titles_handler ); 
}

You'll see I am adding a CodeBox item, setting its position and adding it to the stage. We'll also add a checkbox to this later on.


Step 21: The CodeBox Class

Create the file CodeBox.as in the tabmaker folder and enter the following:

   
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.text.TextField; 
	import flash.desktop.*; 
	 
	public class CodeBox extends Sprite { 
		 
		private var code; 
		private var embed_code; 
		private var album_id; 
		 
		public function CodeBox( code, album ) { 
			this.code = code; 
			album_id = album; 
			embed_code = code.replace(/%s/, album_id ); 
			embed_code = embed_code.replace(/%arg/, 'show_titles=0' ); 
			 
			addEventListener( Event.ADDED_TO_STAGE, init ); 
		} 
		 
		private function init( e ){ 
			code_txt.text = embed_code; 
			code_txt.addEventListener( MouseEvent.CLICK, on_select ); 
			copy_btn.addEventListener( MouseEvent.CLICK, handle_copy ); 
		} 
		 
		public function set_argument( arg ){ 
			embed_code = code.replace(/%s/, album_id ); 
			embed_code = embed_code.replace(/%arg/, arg ); 
			code_txt.text = embed_code; 
		} 
		 
		private function on_select( e ){ 
			e.currentTarget.setSelection( 0, e.currentTarget.length ); 
		} 
		 
		private function handle_copy( e ){ 
			Clipboard.generalClipboard.clear(); 
 			Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, code_txt.text ); 
		} 
	} 
	 
}

We've got a couple of functions here, so let me explain what happens:

  • I have a string defined with the embed code.
  • The string has a token called %s, which will get replaced with the album id, and %arg which gets replaced with the argument show_titles.
  • We are replacing the tokens with the album id and argument, at runtime, in the constructor function.
  • Next, in the init() function, I put the code in the code_txt and I add a click listener.
  • When the user clicks on the text field, I select all the text with the use of the text field's setSelection() method.
  • I also have a click handler on the button, so when the user clicks, I use Clipboard.generalClipboard.setData() method to copy the code to clipboard.

There's also a function set_argument(), which rewrites the embed code with the argument's value ( 1 or 0 ).


Step 22: A Very Fast CheckBox Class

I wanted to use the checkbox bundled with Flash, but I ended up creating my own ( I remember having troubles with that item ). Let's go through it very fast.

Make a little square with the same colors, hit F8 to turn it into a MovieClip and set its class name to CheckBox. Make the two frames of this movieclip as seen in the picture:


Create a new file CheckBox.as in the tabmaker folder and enter the following code:

   
package tabmaker { 
	 
	import flash.display.*; 
	import flash.events.*; 
	import flash.net.*; 
	import flash.text.*; 
	import flash.external.ExternalInterface; 
	import fl.motion.Color; 
	 
	 
	public class CheckBox extends MovieClip { 
		 
		private var ct; 
		public var check_state = 0; 
		private var txt_label; 
		public static const CHECK = 'check'; 
		 
		public function CheckBox( state_arg=0, label_arg='' ) { 
			 
			check_state = state_arg; 
			txt_label = label_arg; 
			ct = new Color(); 
			ct.tintMultiplier = 0.2; 
			ct.tintColor = 0xffffff; 
			 
			addEventListener( Event.ADDED_TO_STAGE, init ); 
		} 
		 
		private function init( e ){ 
			gotoAndStop( check_state+1 ); 
			mouseChildren = false; 
			buttonMode = true; 
			label_txt.text = txt_label; 
			label_txt.autoSize = 'left'; 
			 
			addEventListener( MouseEvent.MOUSE_OVER, m_over ); 
			addEventListener( MouseEvent.MOUSE_OUT, m_out ); 
			addEventListener( MouseEvent.CLICK, m_click ); 
		} 
		 
		private function m_over( e ){ 
			transform.colorTransform = ct; 
		} 
		 
		private function m_out( e ){ 
			transform.colorTransform = new Color(); 
		} 
		 
		private function m_click( e ){ 
			check_state = (Boolean(check_state))? 0 : 1; 
			gotoAndStop( check_state+1 ); 
		} 
	} 
	 
}

Let's see what this class does.

We have a ct variable, which we use to visually transform the checkbox on hover ( no surprises here ). Next, we have a check_state variable, which will be a Boolean, and a txt_label variable, which we'll use to set the textfield.

In the constructor, apart from setting the variables to the passed parameters, we call the init() function on the Event.ADDED_TO_STAGE event. Why ? Because we cannot manipulate any display object until the movieclip is on the display list. Trying to set the text field's text property in the constructor will trigger an error.

Next, in the init() function, we use gotoAndStop() to stop the clip in either frame 1 or frame 2, depending on the check_state variable. As check_state is a boolean returning 0 or 1, in order to go to frame 2 we add 1 to the check_state variable. Simple, huh ?

This happens in the click listener, too, altrough I use Boolean() again to cast the variable to a Boolean.


Step 23: Resetting the Embed Code

We need to create the handler for when the user checks the checkbox, which is titles_handler():

   
private function titles_handler( e ){ 
    cbox.set_argument( 'show_titles='+show_titles.check_state ); 
}

This function is the easiest of them all, I think you can understand what it does. We use the CodeBox.set_argument() method to set the show_titles argument in the embed code. I use the public variable check_state on the CheckBox.

(There is another way of passing the check state, trough the use of a custom event, but we won't get into details here. The important thing is that this works, too.)

Step 24: Make Some Instructions

I have created another function, show_instructions(), which adds the instructions movieclip ( I won't show you how to make that one, though ):

   
private function show_instructions(){ 
    instr = new Instructions(); 
    instr.x = 380; 
    instr.y = 300; 
    addChild( instr ); 
}

I just made a link on the text within Flash, so that the user can go directly to the Static HTML Iframe application ( which we'll use to host the embed code ).

If you test the application now ( making sure there are no hiccups! ) you'll be able to see the generated code, which we'll use in the later parts to load the photos of the album on a Facebook page.


Next Time...

This is the end of the second part of this tutorial. In the next part, we'll tackle the other side of the generator application, the Events Maker, which will allow the user to select a page with events to display on a tab on Facebook. In the end, we'll have a cool application to feature your albums or events, suitable for a business page or concert venue.

Advertisement
Related Posts