Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Look Up Movies With Flash and the Rotten Tomatoes API

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

For some time now I've used Flixter and Rotten Tomatoes to find out which movies were worth watching. Today Rotten Tomatoes is my number one source for obtaining information about a particular film. When I discovered that Rotten Tomatoes had an open API, I knew that this was a perfect opportunity to demonstrate Flash's capabilities to connect with remote services. In this tutorial you will learn how to interact with the Rotten Tomatoes API, as we put together an efficient library that is easy to use and can quickly be expanded.


Final Result Preview

To use the demo below, immediately click the try button to use the default API key provided, or type in your own API key if the default key returns an error.

Type in a keyword and click Go to search; results are displayed within a draggable window. Click a movie poster to maximize its parent window.

For a larger view, click here.

In this tutorial, I'm not going to explain how to build the UI above (although all the code for the UI is provided); I'm going to explain how to use the Rotten Tomatoes API to obtain movie data that you can use in any UI or Flash app.


Prerequisites

This tutorial assumes that you have already registered as developer at the Rotten Tomatoes website and that you understand the concept of inheritance in Flash. You will also need to download the AS3 Core Library if you have not already done so. We will be using this library to parse JSON data returned by Rotten Tomatoes.

Another utility that you will need to run the demo application is the MinimalComps library. This library allows our application to provide a graphical user interface that visualizes data and adds user interaction.


Step 1: The DataLoader Class

We are going to start off with a blank application and then begin filling our application with data. Both the base and the complete applications are available in the source download.

At the time of this writing the Rotten Tomatoes API is currently only available in JSON format. We will need a base class that can prepare and handle different types of data loaded from the internet. Here is a list of formats that this class will handle.

  • JSON
  • XML
  • URL
  • Text

If Rotten Tomatoes updates their API to render XML data, for example, this class will make it easy to swap between formats. The developer can choose the format in which a particular DataLoader object should handle. Every class that loads data from Rotten Tomatoes will extend this class.

Create a new class called DataLoader. The class should extend flash.events.EventDispatcher. Also import the following classes:

 
package { 
	 
	import flash.events.Event; 
	import flash.events.IOErrorEvent; 
	import flash.events.EventDispatcher; 
	import flash.net.URLLoader; 
	import flash.net.URLVariables; 
	import flash.net.URLRequest; 
	import flash.events.ProgressEvent; 
	import com.adobe.serialization.json.JSON; 
	 
	public class DataLoader extends EventDispatcher {

Define the following properties.

 
private var loader:URLLoader; 
private var _vars:URLVariables; 
private var _data:*; 
private var _format:String; 
private var _method:String; 
private var _percentLoaded:uint; 
protected var _source:String; 
protected var _rawData:*; 
private var _method:String;

You'll see the purpose of these properties in a bit. For now just create the constructor.

 
public function DataLoader( source:String ) { 
	 
	super(); 
	loader = new URLLoader(); 
	_source = source; 
	_format = "none"; 
	_method = "GET"; 
}

The loader object will load data into Flash. The _source object is a String that represents the location of the data to load. The _format object is also a String which represents how the loaded data should be handled or parsed.

Create the load method.

 
public function load():void { 
			 
	loader.addEventListener( "complete", onComplete ); 
	loader.addEventListener( "ioError", onIOError ); 
	loader.addEventListener( "progress", onProgress ); 
	var req:URLRequest = new URLRequest( source ); 
	if ( vars ) req.data = vars; 
	req.method = method; 
	_percentLoaded = 0; 
	loader.load( req ); 
}

The load method does exactly what you expect it to: it loads the data from its source. We listen for the COMPLETE, IO_ERROR, and PROGRESS events to be dispatched from the loader object so that the corresponding methods can be called upon the event. If the vars object exists we want to apply the object to the new URLRequest object (req) that we just created. Finally we reset the _percentLoaded property to zero and call the load method from our Loader instance passing the URLRequest object to the parameter.

Now we'll create the event handler methods.

 
private function onComplete( e:Event ):void { 
	 
	loader.removeEventListener( "complete", onComplete ); 
	loader.removeEventListener( "ioError", onIOError ); 
	loader.removeEventListener( "progress", onProgress ); 
	_rawData = loader.data; 
     
	try { 
		 
		handleData(); 
		if ( data ) parseData(); 
	} 
	catch ( error:Error ) { 
		 
		dispatchEvent( new DataLoaderErrorEvent( DataLoaderErrorEvent.INVALID_DATA ) ); 
		return; 
	} 
	 
	dispatchEvent( e ); 
} 
 
private function onIOError( e:IOErrorEvent ):void { 
	 
    loader.removeEventListener( "complete", onComplete ); 
	loader.removeEventListener( "ioError", onIOError ); 
	loader.removeEventListener( "progress", onProgress ); 
	dispatchEvent( e ); 
} 
 
private function onProgress( e:ProgressEvent ):void { 
	 
	_percentLoaded = Math.round( e.bytesLoaded / e.bytesTotal * 100 ); 
	dispatchEvent( e ); 
}

The onComplete method is called when the COMPLETE event is dispatched. The first thing we do is remove the event listeners on the loader since we no longer need to listen for them. The try block contains a call to the handleData method and a call to the parseData method. (We have not created these methods yet but will do so in a moment.)

If either one of these methods produces an error, that error is caught but a DataLoaderErrorEvent is dispatched in its place. (We will also create the DataLoaderErrorEvent class in a moment.)

Finally, we dispatch e, which is the COMPLETE event. There is no need to create a new Event object; we can just recycle the old one since it is no longer being used.

Just as we did in the onComplete method, remove all of the event listeners from the loader within the onIOError method. Then dispatch e which is the IO_ERROR event.

The onProgress method calculates a percentage based upon the amount of bytes that have been loaded and the number of bytes in total.


Step 2: Handling Data

Create the handleData method.

 
private function handleData():void { 
	 
	if ( loader.data ) { 
		 
		switch ( format ) { 
			 
			case DataLoaderFormat.JSON : 
				_data = JSON.decode( rawData ); 
				break; 
			case DataLoaderFormat.TEXT : 
				_data = String( rawData ); 
				break; 
			case DataLoaderFormat.URL : 
				_data = new URLVariables( rawData ); 
				break; 
			case DataLoaderFormat.XML : 
				_data = new XML( rawData ); 
				break; 
			case DataLoaderFormat.NONE : 
			default : 
				// Do nothing 
		} 
	} 
}

The handleData method decodes the loaded data. More specifically, it converts the loaded data to an Object. The type of Object is entirely based on the format.

The JSON format will create a dynamic primitive Object. Use the decode method of the JSON class to decode the json data; this returns a native Object. The XML format creates an XML object; the URL format creates a URLVariables object; and the Text format is simply a String representation of the loaded data.

If the format is set to none, the data will not be packaged or prepared at all.


Step 3: Parsing Data

Create the abstract method, parseData.

 
protected function parseData():void { 
			 
	// Abstract 
}

The parseData function has no implementation because it is a protected method that is meant to be extended and implemented by a subclass. This method is generally going to be used to by a subclass to serialize packaged data into a strict non-dynamic object so that it can be used to by other code more efficiently. This method can even be used to correct specific values or convert dates into the current timezone and so forth.


Step 4: Wrapping Up the DataLoader

All of the private properties of the DataLoader class that begin with an underscore (_), will need some type of access from outside code. Of course this will be accomplished with getter and setter methods. Create the following getters and setters.

 
public function set vars( value:URLVariables ):void { 
	 
	_vars = value; 
} 
 
public function get vars():URLVariables { 
	 
	return _vars; 
} 
 
public function get source():String { 
	 
	return _source; 
} 
 
public function get data():* { 
	 
	return _data; 
} 
 
public function set format( value:String ):void { 
	 
	switch ( value ) { 
		 
		case DataLoaderFormat.JSON : 
		case DataLoaderFormat.NONE : 
		case DataLoaderFormat.TEXT : 
		case DataLoaderFormat.URL : 
		case DataLoaderFormat.XML : 
			_format = value; 
			if ( rawData ) handleData(); 
			break; 
		default : 
			throw new Error( "Invalid format." ); 
	} 
} 
 
public function get format():String { 
	 
    return _format; 
} 
		 
public function get percentLoaded():uint { 
	 
	return _percentLoaded; 
} 
 
public function get rawData():* { 
 
	return _rawData; 
} 
 
public function set method( value:String ):void { 
	 
	switch ( value ) { 
		 
		case URLRequestMethod.GET : 
		case URLRequestMethod.POST : 
			_method = value; 
			break; 
			default : 
				throw new Error( "Invalid method value." ); 
	} 
} 
 
public function get method():String { 
	 
	return _method; 
}

When the format property is set, we check to if the value provided is valid. If it isn't valid an error is thrown.


Step 5: The DataLoaderErrorEvent Class

Now that we have completed the DataLoader class we must create the DataLoaderErrorEvent class before we can move on.

 
package { 
	 
	import flash.events.Event; 
	 
	public static const INVALID_DATA:String = "invalidData"; 
     
	public class DataLoaderErrorEvent extends Event { 
 
		public function DataLoaderErrorEvent( type:String, bubbles:Boolean = false, cancelable:Boolean = false ) { 
			 
			super( type, bubbles, cancelable ); 
		} 
	} 
}

Instead of dispatching a simple ErrorEvent we created this class to dispatch a specific type of ErrorEvent. One that is related to parse error that occurrs in the DataLoader class.


Step 6: Loading Data

Since our DataLoader class is finished, we might as well load some data. Add the following code to the document class of your project:

 
package { 
	 
	import flash.display.MovieClip; 
	import flash.events.Event; 
	import flash.net.URLVariables; 
	 
	public class MyApp extends MovieClip { 
		 
		public static const API_KEY:String = "[your api key]"; 
		 
		public function MyApp() { 
			 
			if ( stage ) init() 
			else addEventListener( "addedToStage", init ); 
		} 
		 
		public function init( e:Event = null ):void { 
			 
			if ( e ) removeEventListener( "addedToStage", init ); 
			 
			var src:String = "http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json"; 
			var loader:DataLoader = new DataLoader( src ); 
			var vars:URLVariables = new URLVariables(); 
			vars.apikey = API_KEY; 
			vars.limit = 0; 
			vars.country = "us"; 
			vars.page = 1; 
			loader.addEventListener( "complete", onDataLoaded ); 
			loader.format = "json"; 
			loader.vars = vars; 
			loader.load(); 
		} 
		 
		private function onDataLoaded( e:Event ):void { 
			 
			trace( e.target.rawData ); 
		} 
	} 
}

Note: Remember to apply your own API_KEY to the above code.

When this code is executed it loads data from the URL http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json. The following GET variables are sent with the request using the URLVariables class (or the vars property of the DataLoader object):

  • apikey - the API Key that was assigned by Rotten Tomatoes for your application.
  • country - the user's country.
  • page - a number that represents the current page of the returned results.
  • page_limit - an integer that specifies how many search results should be displayed per page.

When the above code is executed, the following output is produced by tracing out the rawData from the loader which is accessed as e.target in the onDataLoaded method.

(Note: Your results may vary.)

 
{ 
	"total": 79, 
	"movies": [ 
		{ 
			"id": "771210064", 
			"title": "The Elephant In The Living Room", 
			"year": 2011, 
			"mpaa_rating": "PG", 
			"runtime": 96, 
			"release_dates": { 
				"theater": "2011-04-01", 
				"dvd": "2012-02-06" 
			}, 
			"ratings": { 
				"critics_rating": "Fresh", 
				"critics_score": 82, 
				"audience_rating": "Upright", 
				"audience_score": 87 
			}, 
			"synopsis": "Set against the backdrop of a heated national debate, director Michael Webber takes viewers on a journey deep inside the controversial American subculture of raising the most dangerous animals in the world, as common household pets. This groundbreaking film chronicles the extraordinary lives of two men at the heart of the issue - Tim Harrison, an Ohio police officer whose friend was killed by an exotic pet; and Terry Brumfield, a big-hearted man who struggles to raise two African lions that he loves like his own family. In the first of many unexpected twists, the lives of these two men collide when Terry's male lion escapes its pen and is found attacking cars on a nearby highway. -- (C) Nightfly Entertainment", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/11/15/58/11155801_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/11/15/58/11155801_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/11/15/58/11155801_det.jpg", 
				"original": "http://content7.flixster.com/movie/11/15/58/11155801_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Tim Harrison" 
				}, 
				{ 
					"name": "Terry Brumfield" 
				}, 
				{ 
					"name": "Russ Clear" 
				}, 
				{ 
					"name": "Casey Craig" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1111313" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771210064.json", 
				"alternate": "http://www.rottentomatoes.com/m/the_elephant_in_the_living_room_2010/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771210064/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771210064/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771210064/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771210064/similar.json" 
			} 
		}, 
		{ 
			"id": "771107408", 
			"title": "Burzynski", 
			"year": 2010, 
			"mpaa_rating": "Unrated", 
			"runtime": 108, 
			"release_dates": { 
				"theater": "2010-06-04", 
				"dvd": "2012-02-06" 
			}, 
			"ratings": { 
				"critics_rating": "Rotten", 
				"critics_score": 42, 
				"audience_rating": "Upright", 
				"audience_score": 88 
			}, 
			"synopsis": "BURZYNSKI is the compelling story of a pioneering medical doctor and PhD biochemist who has discovered the genetic mechanism to cure most human cancers. This timely, eye-opening documentary takes the audience through the treacherous, yet victorious, 14-year journey that both Dr. Burzynski and his patients have had to endure in order to obtain FDA-approved clinical trials of Antineoplastons - resulting in the largest and most convoluted defeat against the Food and Drug Administration in American history. Directed by Eric Merola, BURZYNSKI details the heroic plight of the doctor who faced relentless governmental harassment in order to bring his life-saving treatment to the world. Various cancer survivors are presented in the film that chose Dr. Burzynski's treatment instead of surgery, chemotherapy or radiation - with full disclosure of their original medical records to support their full recovery from prior terminal cancer diagnoses. This whistle-blowing film was an Official Selection in the Byron Beach International Film Festival (Australia), San Luis Obispo International Film Festival (CA), Garden State Film Festival (NJ), Palm Beach Film Festival (FL), and Newport Beach Film Festival (CA), where it won the Humanitarian Vision Award. --© Official Site", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/11/13/17/11131773_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/11/13/17/11131773_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/11/13/17/11131773_det.jpg", 
				"original": "http://content7.flixster.com/movie/11/13/17/11131773_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Joe Barton" 
				}, 
				{ 
					"name": "Stanislaw Burzynski" 
				}, 
				{ 
					"name": "Dr. David A. Kessler" 
				}, 
				{ 
					"name": "Arize Onuekwusi" 
				}, 
				{ 
					"name": "David Kessler" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1632703" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771107408.json", 
				"alternate": "http://www.rottentomatoes.com/m/burzynski_2010/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771107408/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771107408/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771107408/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771107408/similar.json" 
			} 
		}, 
		{ 
			"id": "771237427", 
			"title": "King George Vi: The Man Behind The King's Speech", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": "", 
			"release_dates": { 
				"dvd": "2012-02-06" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_rating": "Upright", 
				"audience_score": 70 
			}, 
			"synopsis": "The award winning movie The King's Speech, has taken the film world by storm. The true story of King George VI's struggle to speak in public to the British nation during the crisis of his brother's abdication and the onset of war with Hitler's Germany has captivated the film-going public. King George VI: The Man Behind the King's Speech is a brand new documentary produced to coincide with the award ceremonies and home entertainment release of The King's Speech. Using archive footage of King George VI and new interviews with the grandson of Lionel Logue, the King's speech therapist, this unique documentary charts the life and story of the man who never wished to be King of England and how he overcame his personal limitations for the sake of a nation. The programme is brought right up to date with interviews and insights from the stars of The King's Speech such as Colin Firth, ending with the film's well deserved success at the Academy Awards.", 
			"posters": { 
				"thumbnail": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"profile": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"detailed": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"original": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Colin Firth" 
				}, 
				{ 
					"name": "Mark Logue" 
				}, 
				{ 
					"name": "Tom Hooper" 
				} 
			], 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771237427.json", 
				"alternate": "http://www.rottentomatoes.com/m/king_george_vi_the_man_behind_the_kings_speech/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771237427/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771237427/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771237427/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771237427/similar.json" 
			} 
		}, 
		{ 
			"id": "771262143", 
			"title": "Don't Let Him In", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": "", 
			"release_dates": { 
				"dvd": "2012-02-06" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_rating": "Spilled", 
				"audience_score": 0 
			}, 
			"synopsis": "What if you invited a serial killer on holiday? Handsome, charming, and arrogant, Tristan has picked up Mandy for a hot one-night stand. The love-struck girl then invites him to a rural weekend getaway with her brother, Calvin, and his girlfriend, Paige, an emergency room nurse. But when the group learn a sadistic serial killer dubbed the Tree Surgeon is plaguing the area, Paige is forced to confront the deeply disturbing truth about Tristan.", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/11/16/23/11162345_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/11/16/23/11162345_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/11/16/23/11162345_det.jpg", 
				"original": "http://content7.flixster.com/movie/11/16/23/11162345_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Gordon Alexander", 
					"characters": [ 
						"Tristan" 
					] 
				}, 
				{ 
					"name": "Sophie Linfield", 
					"characters": [ 
						"Paige" 
					] 
				}, 
				{ 
					"name": "Sam Hazeldine", 
					"characters": [ 
						"Shawn" 
					] 
				}, 
				{ 
					"name": "Gemma Harvey", 
					"characters": [ 
						"Mandy" 
					] 
				}, 
				{ 
					"name": "Rhys Meredith", 
					"characters": [ 
						"Calvin" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1400515" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771262143.json", 
				"alternate": "http://www.rottentomatoes.com/m/dont_let_him_in/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771262143/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771262143/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771262143/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771262143/similar.json" 
			} 
		}, 
		{ 
			"id": "9395", 
			"title": "Lady and the Tramp", 
			"year": 1955, 
			"mpaa_rating": "G", 
			"runtime": 76, 
			"critics_consensus": "A nostalgic charmer, Lady and the Tramp's token sweetness is mighty but the songs and richly colored animation are technically superb and make for a memorable experience.", 
			"release_dates": { 
				"theater": "1955-06-22", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Fresh", 
				"critics_score": 88, 
				"audience_rating": "Upright", 
				"audience_score": 73 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/10/84/96/10849641_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/10/84/96/10849641_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/10/84/96/10849641_det.jpg", 
				"original": "http://content7.flixster.com/movie/10/84/96/10849641_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Barbara Luddy", 
					"characters": [ 
						"Lady" 
					] 
				}, 
				{ 
					"name": "Larry Roberts", 
					"characters": [ 
						"Tramp" 
					] 
				}, 
				{ 
					"name": "Bill Thompson", 
					"characters": [ 
						"Bull", 
						"Dachsie", 
						"Jock" 
					] 
				}, 
				{ 
					"name": "Stan Freberg", 
					"characters": [ 
						"Beaver" 
					] 
				}, 
				{ 
					"name": "Verna Felton", 
					"characters": [ 
						"Aunt Sarah" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "0048280" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/9395.json", 
				"alternate": "http://www.rottentomatoes.com/m/lady_and_the_tramp/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/9395/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/9395/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/9395/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/9395/similar.json" 
			} 
		}, 
		{ 
			"id": "771041051", 
			"title": "A Very Harold & Kumar Christmas", 
			"year": 2011, 
			"mpaa_rating": "R", 
			"runtime": 89, 
			"critics_consensus": "Still raunchy, still irreverent, and still hit-and-miss, this Harold & Kumar outing also has a Christmas miracle: The audience gets to see the sweeter side of the duo.", 
			"release_dates": { 
				"theater": "2011-11-04", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Fresh", 
				"critics_score": 69, 
				"audience_rating": "Upright", 
				"audience_score": 66 
			}, 
			"synopsis": "Following years of growing apart, Harold Lee (Cho) and Kumar Patel (Penn) have replaced each other with new friends and are preparing for their respective Yuletide celebrations. But when a mysterious package mistakenly arrives at Kumar's door on Christmas Eve, his attempt to redirect it to Harold's house ends with the \"high grade\" contents-and Harold's father-in-law's prize Christmas tree-going up in smoke. With his in-laws out of the house for the day, Harold decides to cover his tracks, rather than come clean. Reluctantly embarking on another ill-advised journey with Kumar through New York City, their search for the perfect replacement tree takes them through party heaven-and almost blows Christmas Eve sky high. -- (C) Warner Bros", 
			"posters": { 
				"thumbnail": "http://content9.flixster.com/movie/11/16/04/11160419_mob.jpg", 
				"profile": "http://content9.flixster.com/movie/11/16/04/11160419_pro.jpg", 
				"detailed": "http://content9.flixster.com/movie/11/16/04/11160419_det.jpg", 
				"original": "http://content9.flixster.com/movie/11/16/04/11160419_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "John Cho", 
					"characters": [ 
						"Harold" 
					] 
				}, 
				{ 
					"name": "Kal Penn", 
					"characters": [ 
						"Kumar" 
					] 
				}, 
				{ 
					"name": "Neil Patrick Harris", 
					"characters": [ 
						"Neil Patrick Harris" 
					] 
				}, 
				{ 
					"name": "Patton Oswalt", 
					"characters": [ 
						"Mall Santa" 
					] 
				}, 
				{ 
					"name": "Danny Trejo", 
					"characters": [ 
						"Mr. Perez" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1268799" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771041051.json", 
				"alternate": "http://www.rottentomatoes.com/m/very_harold_and_kumar_christmas/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771041051/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771041051/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771041051/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771041051/similar.json" 
			} 
		}, 
		{ 
			"id": "770680916", 
			"title": "Fireflies in the Garden", 
			"year": 2011, 
			"mpaa_rating": "R", 
			"runtime": 98, 
			"critics_consensus": "Despite boasting a stellar cast, Fireflies in the Garden is just tedious, dull and predictable melodrama. Instantly forgettable.", 
			"release_dates": { 
				"theater": "2011-10-14", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Rotten", 
				"critics_score": 21, 
				"audience_rating": "Spilled", 
				"audience_score": 46 
			}, 
			"synopsis": "To an outsider, the Taylors are the very picture of the successful American family: Charles (Willem Dafoe) is a tenured professor on track to become university president, son Michael (Ryan Reynolds) is a prolific and well-known romance novelist, daughter Ryne (Shannon Lucio) is poised to enter a prestigious law school, and on the day we are introduced to them, matriarch Lisa (Julia Roberts) will graduate from college-decades after leaving to raise her children. But when a serious accident interrupts the celebration, the far more nuanced reality of this Midwestern family's history and relationships come to light. -- (C) Senator", 
			"posters": { 
				"thumbnail": "http://content9.flixster.com/movie/11/16/05/11160563_mob.jpg", 
				"profile": "http://content9.flixster.com/movie/11/16/05/11160563_pro.jpg", 
				"detailed": "http://content9.flixster.com/movie/11/16/05/11160563_det.jpg", 
				"original": "http://content9.flixster.com/movie/11/16/05/11160563_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Julia Roberts", 
					"characters": [ 
						"Lisa Taylor" 
					] 
				}, 
				{ 
					"name": "Ryan Reynolds", 
					"characters": [ 
						"Michael Taylor" 
					] 
				}, 
				{ 
					"name": "Willem Dafoe", 
					"characters": [ 
						"Charles Taylor" 
					] 
				}, 
				{ 
					"name": "Emily Watson", 
					"characters": [ 
						"Jane Lawrence" 
					] 
				}, 
				{ 
					"name": "Carrie-Anne Moss", 
					"characters": [ 
						"Kelly Hanson" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "0961108" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/770680916.json", 
				"alternate": "http://www.rottentomatoes.com/m/10008601-fireflies_in_the_garden/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/770680916/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/770680916/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/770680916/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/770680916/similar.json" 
			} 
		}, 
		{ 
			"id": "770771664", 
			"title": "The Rebound", 
			"year": 2009, 
			"mpaa_rating": "R", 
			"runtime": 97, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Rotten", 
				"critics_score": 45, 
				"audience_score": 48 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://content6.flixster.com/movie/11/15/53/11155380_mob.jpg", 
				"profile": "http://content6.flixster.com/movie/11/15/53/11155380_pro.jpg", 
				"detailed": "http://content6.flixster.com/movie/11/15/53/11155380_det.jpg", 
				"original": "http://content6.flixster.com/movie/11/15/53/11155380_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Catherine Zeta-Jones" 
				}, 
				{ 
					"name": "Justin Bartha" 
				}, 
				{ 
					"name": "Sam Robards" 
				}, 
				{ 
					"name": "Lisa Paige Robinson" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1205535" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/770771664.json", 
				"alternate": "http://www.rottentomatoes.com/m/10009980-rebound/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/770771664/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/770771664/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/770771664/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/770771664/similar.json" 
			} 
		}, 
		{ 
			"id": "771207996", 
			"title": "Anonymous", 
			"year": 2011, 
			"mpaa_rating": "PG-13", 
			"runtime": 129, 
			"critics_consensus": "Roland Emmerich delivers his trademark visual and emotional bombast, but the more Anonymous stops and tries to convince the audience of its half-baked theory, the less convincing it becomes.", 
			"release_dates": { 
				"theater": "2011-10-28", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Rotten", 
				"critics_score": 47, 
				"audience_rating": "Upright", 
				"audience_score": 63 
			}, 
			"synopsis": "Set in the political snake-pit of Elizabethan England, Anonymous speculates on an issue that has for centuries intrigued academics and brilliant minds such as Mark Twain, Charles Dickens, and Sigmund Freud, namely: who actually created the body of work credited to William Shakespeare? Experts have debated, books have been written, and scholars have devoted their lives to protecting or debunking theories surrounding the authorship of the most renowned works in English literature. Anonymous poses one possible answer, focusing on a time when scandalous political intrigue, illicit romances in the Royal Court, and the schemes of greedy nobles lusting for the power of the throne were brought to light in the most unlikely of places: the London stage. -- (C) Sony Pictures", 
			"posters": { 
				"thumbnail": "http://content6.flixster.com/movie/11/15/84/11158480_mob.jpg", 
				"profile": "http://content6.flixster.com/movie/11/15/84/11158480_pro.jpg", 
				"detailed": "http://content6.flixster.com/movie/11/15/84/11158480_det.jpg", 
				"original": "http://content6.flixster.com/movie/11/15/84/11158480_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Ed Hogg", 
					"characters": [ 
						"Robert Cecil" 
					] 
				}, 
				{ 
					"name": "Rhys Ifans", 
					"characters": [ 
						"Earl of Oxford" 
					] 
				}, 
				{ 
					"name": "Vanessa Redgrave", 
					"characters": [ 
						"Queen Elizabeth I" 
					] 
				}, 
				{ 
					"name": "David Thewlis", 
					"characters": [ 
						"William Cecil" 
					] 
				}, 
				{ 
					"name": "Sebastian Armesto", 
					"characters": [ 
						"Ben Johnson" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1521197" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771207996.json", 
				"alternate": "http://www.rottentomatoes.com/m/anonymous_2011/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771207996/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771207996/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771207996/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771207996/similar.json" 
			} 
		}, 
		{ 
			"id": "771224201", 
			"title": "Project Nim", 
			"year": 2011, 
			"mpaa_rating": "PG-13", 
			"runtime": 93, 
			"critics_consensus": "Equal parts hilarious, poignant, and heartbreaking, Project Nim not only tells a compelling story masterfully, but also raises the flag on the darker side of human nature.", 
			"release_dates": { 
				"theater": "2011-07-08", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Certified Fresh", 
				"critics_score": 98, 
				"audience_rating": "Upright", 
				"audience_score": 85 
			}, 
			"synopsis": "From the team behind Man on Wire comes the story of Nim, the chimpanzee who in the 1970s became the focus of a landmark experiment which aimed to show that an ape could learn to communicate with language if raised and nurtured like a human child. Following Nim's extraordinary journey through human society, and the enduring impact he makes on the people he meets along the way, the film is an unflinching and unsentimental biography of an animal we tried to make human. What we learn about his true nature - and indeed our own - is comic, revealing and profoundly unsettling. -- (C) Roadside Attractions", 
			"posters": { 
				"thumbnail": "http://content9.flixster.com/movie/11/15/76/11157647_mob.jpg", 
				"profile": "http://content9.flixster.com/movie/11/15/76/11157647_pro.jpg", 
				"detailed": "http://content9.flixster.com/movie/11/15/76/11157647_det.jpg", 
				"original": "http://content9.flixster.com/movie/11/15/76/11157647_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Garry Harris" 
				}, 
				{ 
					"name": "Stephanie Lafarge" 
				}, 
				{ 
					"name": "Jenny Lee Wright" 
				}, 
				{ 
					"name": "Laura Ann Petitto" 
				}, 
				{ 
					"name": "Joyce Butler" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1814836" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771224201.json", 
				"alternate": "http://www.rottentomatoes.com/m/project_nim/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771224201/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771224201/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771224201/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771224201/similar.json" 
			} 
		}, 
		{ 
			"id": "771228625", 
			"title": "Knuckle", 
			"year": 2011, 
			"mpaa_rating": "R", 
			"runtime": 85, 
			"critics_consensus": "A gritty documentary that captures the brutality and banality of bare-knuckle fights among Irish Travellers.", 
			"release_dates": { 
				"theater": "2011-12-09", 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_rating": "Certified Fresh", 
				"critics_score": 95, 
				"audience_rating": "Upright", 
				"audience_score": 67 
			}, 
			"synopsis": "An epic 12-year journey into in the world of an Irish Traveller community, Knuckle takes us inside their brutal, secretive and exhilarating bare-knuckle fighting lives. Chronicling a history of violent feuding between rival families, the story focuses on two brothers as they fight for their reputations and the honour of their family name. Brutal, yet captivating and ultimately moving, this unforgettable documentary offers an exclusive insight into the world of Irish Travellers and the lengths they will go to protect their family name. -- (C) Official Site", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/11/16/03/11160381_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/11/16/03/11160381_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/11/16/03/11160381_det.jpg", 
				"original": "http://content7.flixster.com/movie/11/16/03/11160381_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Ian Palmer", 
					"characters": [ 
						"Ian Palmer", 
						"Narrator" 
					] 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1606259" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771228625.json", 
				"alternate": "http://www.rottentomatoes.com/m/knuckle_2011/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771228625/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771228625/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771228625/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771228625/similar.json" 
			} 
		}, 
		{ 
			"id": "771229806", 
			"title": "The Sunset Limited", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": 90, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_score": 88 
			}, 
			"synopsis": "A God-fearing ex-con (Samuel L. Jackson) saves a despondent college professor (Tommy Lee Jones) from throwing himself in front of a speeding subway train, and struggles to comprehend the suicidal man's unwavering despair during a series of intense philosophical debates. Adapted from the play by Cormac McCarthy (All the Pretty Horses, The Road). ~ Jason Buchanan, Rovi", 
			"posters": { 
				"thumbnail": "http://content6.flixster.com/movie/11/16/06/11160632_mob.jpg", 
				"profile": "http://content6.flixster.com/movie/11/16/06/11160632_pro.jpg", 
				"detailed": "http://content6.flixster.com/movie/11/16/06/11160632_det.jpg", 
				"original": "http://content6.flixster.com/movie/11/16/06/11160632_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Tommy Lee Jones" 
				}, 
				{ 
					"name": "Samuel L. Jackson" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1510938" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771229806.json", 
				"alternate": "http://www.rottentomatoes.com/m/the_sunset_limited/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771229806/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771229806/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771229806/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771229806/similar.json" 
			} 
		}, 
		{ 
			"id": "771264191", 
			"title": "Geek Charming", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": 90, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_score": 76 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"profile": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"detailed": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"original": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Sarah Hyland", 
					"characters": [ 
						"Dylan Shoenfield" 
					] 
				}, 
				{ 
					"name": "Matt Prokop", 
					"characters": [ 
						"Josh Rosen" 
					] 
				}, 
				{ 
					"name": "Sasha Pieterse", 
					"characters": [ 
						"Amy Loubalu" 
					] 
				}, 
				{ 
					"name": "Jordan Nichols", 
					"characters": [ 
						"Asher" 
					] 
				}, 
				{ 
					"name": "Vanessa Morgan", 
					"characters": [ 
						"Hannah" 
					] 
				} 
			], 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771264191.json", 
				"alternate": "http://www.rottentomatoes.com/m/geek_charming/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771264191/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771264191/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771264191/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771264191/similar.json" 
			} 
		}, 
		{ 
			"id": "771263702", 
			"title": "Fred 2: Night Of The Living Fred", 
			"year": 2011, 
			"mpaa_rating": "G", 
			"runtime": 83, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_score": 53 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"profile": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"detailed": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"original": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Lucas Cruikshank", 
					"characters": [ 
						"Fred Figglehorn" 
					] 
				}, 
				{ 
					"name": "John Cena", 
					"characters": [ 
						"Fred's Dad" 
					] 
				}, 
				{ 
					"name": "Daniella Monet", 
					"characters": [ 
						"Bertha" 
					] 
				}, 
				{ 
					"name": "Ariel Winter", 
					"characters": [ 
						"Talia" 
					] 
				}, 
				{ 
					"name": "Jake Weary" 
				} 
			], 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771263702.json", 
				"alternate": "http://www.rottentomatoes.com/m/fred_2_night_of_the_living_fred/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771263702/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771263702/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771263702/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771263702/similar.json" 
			} 
		}, 
		{ 
			"id": "771267729", 
			"title": "Last Man Standing", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": 84, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_score": 33 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://content7.flixster.com/movie/11/16/31/11163145_mob.jpg", 
				"profile": "http://content7.flixster.com/movie/11/16/31/11163145_pro.jpg", 
				"detailed": "http://content7.flixster.com/movie/11/16/31/11163145_det.jpg", 
				"original": "http://content7.flixster.com/movie/11/16/31/11163145_ori.jpg" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Anthony Michael Hall" 
				}, 
				{ 
					"name": "Catherine Bell" 
				}, 
				{ 
					"name": "Mekhi Phifer" 
				}, 
				{ 
					"name": "Ele Bardha" 
				}, 
				{ 
					"name": "Tiffani Elise Edwards" 
				} 
			], 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771267729.json", 
				"alternate": "http://www.rottentomatoes.com/m/771267729/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771267729/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771267729/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771267729/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771267729/similar.json" 
			} 
		}, 
		{ 
			"id": "771242052", 
			"title": "Gokudo heiki (Yakuza Weapon)", 
			"year": 2011, 
			"mpaa_rating": "Unrated", 
			"runtime": 105, 
			"release_dates": { 
				"dvd": "2012-02-07" 
			}, 
			"ratings": { 
				"critics_score": -1, 
				"audience_score": 22 
			}, 
			"synopsis": "", 
			"posters": { 
				"thumbnail": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"profile": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"detailed": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif", 
				"original": "http://images.rottentomatoescdn.com/images/redesign/poster_default.gif" 
			}, 
			"abridged_cast": [ 
				{ 
					"name": "Dennis Gunn" 
				}, 
				{ 
					"name": "Cay Izumi" 
				}, 
				{ 
					"name": "Shinji Kasahara" 
				}, 
				{ 
					"name": "Mei Kurokawa" 
				}, 
				{ 
					"name": "Akaji Maro" 
				} 
			], 
			"alternate_ids": { 
				"imdb": "1669604" 
			}, 
			"links": { 
				"self": "http://api.rottentomatoes.com/api/public/v1.0/movies/771242052.json", 
				"alternate": "http://www.rottentomatoes.com/m/gokudo_heiki/", 
				"cast": "http://api.rottentomatoes.com/api/public/v1.0/movies/771242052/cast.json", 
				"clips": "http://api.rottentomatoes.com/api/public/v1.0/movies/771242052/clips.json", 
				"reviews": "http://api.rottentomatoes.com/api/public/v1.0/movies/771242052/reviews.json", 
				"similar": "http://api.rottentomatoes.com/api/public/v1.0/movies/771242052/similar.json" 
			} 
		} 
	], 
	"links": { 
		"self": "http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit=16&country=us&page=1", 
		"next": "http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit=16&country=us&page=2", 
		"alternate": "http://www.rottentomatoes.com/dvd/upcoming.json" 
	}, 
	"link_template": "http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit={results-per-page}&page={page-number}&country={country-code}" 
}

For those of you that find this data to be like an alien language, the returned data is in the JSON format or JavaScript Object Notation (read more about this here). This is probably not how you want your users to see the data. This is where the as3corelib comes into play.

In the example above, we traced the rawData. Now try changing the line of code within the onDataLoaded method to trace loader.data.total.

 
trace( "Number of Results: " + e.target.data.total );

The total property of the JSON object is the number of results that were returned by Rotten Tomatoes. We'll utilize most of the data returned in later steps. Next we're going to begin extending the DataLoader class to better fit the needs of our application.


Step 7: Loading Tomatoes

Now that we know how to load data using the DataLoader class, let's improve our implementation. Create the TomatoLoader class.

 
package { 
	 
	import flash.net.URLVariables; 
    	 
	public class TomatoLoader extends DataLoader { 
		 
		private var _pageLimit:uint; 
		private var _limit:uint; 
		private var _total:uint; 
		private var _country:String; 
		private var _pageNumber:uint; 
		private var _links:TomatoLinks; 
		private var _numOfPages:uint; 
		 
        public static const API_KEY:String = "[your api key]"; 
         
		public function TomatoLoader( source:String ) { 
			 
			super( source ); 
			super.format = "json"; 
			_pageLimit = 16; 
			_limit = 16; 
			_country = "us"; 
			_pageNumber = 0; 
			_numOfPages = 0; 
		} 
		 
		protected override function parseData():void { 
			 
			var json:Object = data as Object; 
			 
			if ( json.hasOwnProperty( "error" ) ) { 
				 
				dispatchEvent( new TomatoLoaderErrorEvent( TomatoLoaderErrorEvent.API_ERROR, json.error ) ); 
				return; 
			} 
			 
			if ( json.hasOwnProperty( "links" ) ) { 
				 
				_links = new TomatoLinks( json.links ); 
			} 
			 
			_total = ( json.total )? json.total : 0; 
			if ( _pageNumber == 0 ) _pageNumber = 1; 
			_numOfPages = Math.ceil( _total / _pageLimit ); 
		} 
		 
		public override function load():void { 
			 
			if ( !vars ) vars = new URLVariables(); 
			vars.apiKey = API_KEY; 
			vars.page_limit = pageLimit; 
			if ( pageNumber > 0 ) vars.page = pageNumber; 
			vars.limit = limit; 
			vars.country = country; 
			super.load(); 
		} 
		 
		public function clear():void { 
			 
			// Abstract 
		} 
		 
		public override function set format( value:String ):void { 
			 
			trace( "Warning: This property has been overriden and can no longer be set." ); 
		} 
		 
		public function set pageLimit( value:uint ):void { 
			 
			if ( value < 1 ) value = 1; 
			_pageLimit = value; 
		} 
		 
		public function get pageLimit():uint { 
			 
			return _pageLimit; 
		} 
		 
		public function set limit( value:uint ):void { 
			 
			if ( value < 1 ) value = 1; 
			_limit = value; 
		} 
		 
		public function get limit():uint { 
			 
			return _limit; 
		} 
		 
		public function get total():uint { 
			 
			return _total; 
		} 
		 
		public function set country( value:String ):void { 
			 
			if ( value ) _country = value 
			else throw new Error( "Country is not optional and cannot be null." ); 
		} 
		 
		public function get country():String { 
			 
			return _country; 
		} 
		 
		public function set pageNumber( value:uint ):void { 
			 
			if ( value > 0 ) { 
				 
				_pageNumber = value; 
			} 
		} 
		 
		public function get pageNumber():uint { 
			 
			return _pageNumber; 
		} 
		 
		public function get links():TomatoLinks { 
			 
			return _links; 
		} 
		 
		public function get numOfPages():uint { 
			 
			return _numOfPages; 
		} 
	} 
}

Let me summarize this class's implementation. This class simplifies that process of loading data from Rotten Tomatoes. This class should also be treated as abstract, meaning it is meant to be extended. Here we have removed the ability to set the format property, since at the time of this writing the API only exists in the JSON format - but this restriction can be removed later.

The following properties are related to GET variables that are to be sent with the request to the Rotten Tomatoes API:

  • _pageLimit
  • _pageNumber
  • _country

We've already seen how their corresponding GET variables are used, as well as the total property from the JSON Object. All we are doing here is storing their values in instance properties so that they can be manipulated easily.

The _limit property is new to us. This property represents a limit on the number of results that should be returned by Rotten Tomatoes. (Note: This property may not apply to all requests, as only some API methods support it.)

We have overriden the abstract parseData method and have added some extra implementation. In the parseData method the number of pages (or _numOfPages) is calculated. The number of pages is equal to the total number of results divided amount of results per page.

We also override the load method so that we can make sure that our GET variables are being sent along with the request.

The TomatoLoader class dispatches the TomatoLoaderErrorEvent, which we'll create next.


Step 8: The TomatoLoaderDataEvent

Create the TomatoLoaderDataEvent class.

 
package { 
	 
	import flash.events.Event; 
	 
	public class TomatoLoaderErrorEvent extends Event { 
		 
		private var _message:String; 
		 
		public static const API_ERROR:String = "apiError"; 
		 
		public function TomatoLoaderErrorEvent( type:String, message:String, bubbles:Boolean = false, cancelable:Boolean = false ) { 
			 
			super( type, bubbles, cancelable ); 
			_message = message; 
		} 
         
		public function get message():String { 
			 
			return _message; 
		} 
	} 
}

There isn't much to explain here. The message property is read-only and represents the error message that is returned from the Rotten Tomatoes API.


Step 9: Loading Movies

Our new DataLoader, the TomatoLoader, is going to be useful, but our application requires more. We now need to create one more abstract class that extends the TomatoLoader class, and is specifically for loading movie information.

Create the MovieLoader class.

 
package { 
		 
	public class MovieLoader extends TomatoLoader { 
		 
		private var _movies:Array; 
 
		public function MovieLoader( source:String ) { 
			 
			super( source ); 
			_movies = []; 
		} 
 
		protected override function parseData():void { 
			 
			super.parseData(); 
			var json:Object = data as Object; 
			 
			if ( json.hasOwnProperty( "movies" ) ) { 
				 
				for each( var movie:Object in json.movies ) { 
					 
					_movies.push( new TomatoMovie( movie ) ); 
				} 
			} 
		} 
		 
		public override function clear():void { 
			 
			_movies = []; 
		} 
		 
		public function get movies():Array { 
			 
			return _movies; 
		} 
	} 
}

This class adds implementation that further parses data about movies. TomatoMovie objects are generated from the data property and are added to the movies Array. When this class dispatches the COMPLETE event, we will have a complete list of movies for a particular page.


Step 10: Links

You should have noticed that there was a links property which was of the type TomatoLinks. We're going to create this object now.

 
package { 
		 
	public class TomatoLinks { 
 
		private var _self:String; 
		private var _alternative:String; 
		private var _template:String; 
		private var _next:String; 
		 
		public function TomatoLinks( links:Object ) { 
			 
			super(); 
			parse( links ); 
		} 
		 
		private function parse( links:Object ):void { 
			 
			_self = links.self; 
			_alternative = links.alternative; 
			_template = links.link_template; 
			_next = links.next; 
		} 
		 
		public function get self():String { 
			 
			return _self; 
		} 
		 
		public function get alternative():String { 
			 
			return _alternative; 
		} 
		 
		public function get template():String { 
			 
			return _template; 
		} 
		 
		public function get next():String { 
			 
			return _next; 
		} 
	} 
}

The parse method parses the JSON Object into a TomatoLinks object. We have read-only access to all of the properties using getter methods.


Step 11: The TomatoMovie Class

The TomatoMovie object will consist of properties and objects that represent data about a particular movie, serialized into a static Object (static meaning the opposite of dynamic).

 
package { 
	 
	public class TomatoMovie { 
		 
		private var _id:String; 
		private var _title:String; 
		private var _year:int; 
		private var _mpaaRating:String; 
		private var _runtime:int; 
		private var _releaseDates:TomatoMovieDates; 
		private var _ratings:TomatoMovieRatings; 
		private var _synopsis:String; 
		private var _posters:TomatoMoviePosters; 
		private var _abridgedCast:Array; 
		private var _cast:String; 
		private var _clips:String; 
		private var _reviews:String; 
		private var _similar:String; 
			 
		public function TomatoMovie( movie:Object ) { 
			 
			super(); 
			_abridgedCast = []; 
			parse( movie ); 
		} 
         
		private function parse( movie:Object ):void { 
			 
			_id = movie.id; 
			_title = movie.title; 
			_year = movie.year; 
			_mpaaRating = movie.mpaa_rating; 
			_runtime = movie.runtime; 
			_releaseDates = new TomatoMovieDates( movie.release_dates ); 
			_ratings = new TomatoMovieRatings( movie.ratings ); 
			_synopsis = movie.synopsis; 
			_posters = new TomatoMoviePosters( movie.posters ); 
			 
			if ( movie.hasOwnProperty( "abridged_cast" ) ) { 
				 
				for each( var actor:Object in movie.abridged_cast ) { 
					 
					_abridgedCast.push( new TomatoMovieActor( actor ) ); 
				} 
			} 
			 
			_cast = movie.links.cast; 
			_clips = movie.links.clips; 
			_reviews = movie.reviews; 
			_similar = movie.similar; 
		} 
         
		public function get id():String { 
			 
			return _id; 
		} 
		 
		public function get title():String { 
			 
			return _title; 
		} 
		 
		public function get year():int { 
			 
			return _year; 
		} 
		 
		public function get mpaaRating():String { 
			 
			return _mpaaRating; 
		} 
		 
		public function get runtime():int { 
			 
			return _runtime; 
		} 
		 
		public function get ratings():TomatoMovieRatings { 
			 
			return _ratings; 
		} 
		 
		public function get synopsis():String { 
			 
			return _synopsis; 
		} 
		 
		public function get posters():TomatoMoviePosters { 
			 
			return _posters; 
		} 
		 
		public function get abridgedCast():Array { 
			 
			return _abridgedCast; 
		} 
		 
		public function get cast():String { 
			 
			return _cast; 
		} 
		 
		public function get clips():String { 
			 
			return _clips; 
		} 
		 
		public function get reviews():String { 
			 
			return _reviews; 
		} 
		 
		public function get similar():String { 
			 
			return _similar; 
		} 
	} 
}

The parse method parses the JSON Object into a TomatoMovie object. It is very, very basic. The TomatoMovie class is just a more organized and well-defined collection of the JSON data. Unlike the JSON Object, which is generated at runtime, the TomatoMovie object is compiled. In other words it is more exclusive.

You're probably wondering why we created the TomatoMovie object and the TomatoLinks object if their corresponding JSON Object contains the same data. I have an answer for you: we want to provide read-only access to the data. We don't want the developer to accidently set (overwrite) any data.

If this seems foolish or unlikely to you I have another answer... code hinting. Many Flash IDEs like FlashDevelop and Flash CS5 Professional contain a code hinting feature, which lets us see which methods and properties exists on an object and what data types they are. By encoding this in an actual AS3 object, we can know exactly what data to expect before we compile our application.


Step 13: The Actors

Create the TomatoMovieActor class.

 
package { 
			 
		public class TomatoMovieActor { 
	 
		private var _name:String; 
		private var _characters:Array; 
							 
		public function TomatoMovieActor( actor:Object ) { 
			 
			super(); 
			parse( actor ); 
		} 
		 
		private function parse( actor:Object ):void { 
			 
			_name = actor.name; 
			_characters = actor.characters; 
		} 
							 
		public function get name():String { 
			 
			return _name; 
		} 
		 
		public function get characters():Array { 
			 
			return _characters; 
		} 
	} 
}

We use the parse to parse the JSON Object as we did before.


Step 14: Dates, Posters, and Ratings

Create the TomatoMovieDates class.

 
package { 
			 
	public class TomatoMovieDates { 
		 
		private var _theater:String; 
		private var _dvd:String; 
							 
		public function TomatoMovieDates( dates:Object ) { 
			 
			super(); 
			parse( dates ); 
		} 
		 
		private function parse( dates:Object ):void { 
			 
			_theater = dates.theater; 
			_dvd = dates.dvd; 
		} 
							 
		public function get theater():String { 
			 
			return _theater; 
		}	 
	} 
}

Create the TomatoMoviePoster class.

 
package { 
		 
	public class TomatoMoviePosters { 
	 
		private var _thumbnail:String; 
		private var _profile:String; 
		private var _detailed:String; 
		private var _original:String; 
							 
		public function TomatoMoviePosters( posters:Object ) { 
			 
			super(); 
			parse( posters ); 
		} 
		 
		private function parse( posters:Object ):void { 
			 
			_thumbnail = posters.thumbnail; 
			_profile = posters.profile; 
			_detailed = posters.detailed; 
			_original = posters.original; 
		} 
	 
		public function get thumbnail():String { 
			 
			return _thumbnail; 
		} 
		 
		public function get profile():String { 
			 
			return _profile; 
		} 
		 
		public function get detailed():String { 
			 
			return _detailed; 
		} 
		 
		public function get original():String { 
			 
			return _original; 
		} 
	} 
}

Create the TomatoMovieRatings class.

 
package  { 
				 
	public class TomatoMovieRatings { 
		 
		private var _criticsScore:int; 
		private var _criticsRating:String; 
		private var _audienceScore:int; 
		private var _audienceRating:String; 
						 
		public function TomatoMovieRatings( ratings:Object ) { 
			 
			super(); 
			parse( ratings ); 
		} 
		 
		private function parse( ratings:Object ):void { 
			 
			_criticsRating = ratings.critics_rating; 
			_criticsScore = ratings.critics_score; 
			_audienceRating = ratings.audience_rating; 
			_audienceScore = ratings.audience_score; 
		} 
		 
		public function get criticsRating():String { 
			 
			return _criticsRating; 
		} 
		 
		public function get criticsScore():int { 
			 
			return _criticsScore; 
		} 
		 
		public function get audienceRating():String { 
			 
			return _audienceRating; 
		} 
		 
		public function get audienceScore():int { 
			 
			return _audienceScore; 
		} 
	} 
}

Step 15: Filling an Empty App

Now we have code that is efficient enough to begin filling in our application with data. Let's take another look at our empty application.

(Remember, this empty project is available in the source download. I'm not going to explain how to create it in this tutorial.)

First let's load and trace data from the objects that we have already created. Try loading movies currently in the Box Office with your application and displaying the first movie returned. You can find the method to load from the Rotten Tomatoes API Documentation. You can either use your own application with its own GUI or you can use the application provided in the source. Here's what my code looks like.

 
package { 
		 
	import flash.display.DisplayObject; 
	import flash.display.Sprite; 
	import flash.events.Event; 
	import flash.events.MouseEvent; 
	import flash.events.ProgressEvent; 
	import flash.events.IOErrorEvent; 
	import flash.system.Security; 
	import com.bit101.components.*; 
	 
	public class MyApp extends Sprite { 
		 
		private var searchLabel:Label; 
		private var searchTxt:Text; 
		private var goBtn:PushButton; 
		private var isSearching:Boolean; 
		private var windows:Array; 
		private var windowsMinimized:Boolean; 
		private var maximized:MovieWindow; 
		private var options:ComboBox; 
		private var pane:ScrollPane; 
		private var currentLoader:MovieLoader; 
		private var pages:Array; 
		private var pagesPane:ScrollPane; 
				 
		public static const SPACING:int = 10; 
		public static const WINDOW_WIDTH:int = 300; 
		public static const WINDOW_HEIGHT:int = 200; 
						 
		public function MyApp() { 
			 
			super(); 
			searchLabel = new Label(); 
			searchLabel.text = "Search: "; 
			searchLabel.width = 40; 
			searchTxt = new Text(); 
			searchTxt.height = 20; 
			goBtn = new PushButton(); 
			goBtn.label = "Go"; 
			goBtn.width = 30; 
			windows = []; 
			options = new ComboBox(); 
			pane = new ScrollPane(); 
			pages = []; 
			pagesPane = new ScrollPane(); 
			pagesPane.height = 30; 
			if ( stage ) init() 
			else addEventListener( "addedToStage", onAdded ); 
		} 
		 
		private function init():void { 
			 
			stage.align = "TL"; 
			stage.scaleMode = "noScale"; 
			 
			addChild( pane ); 
			addChild( searchLabel ); 
			addChild( searchTxt ); 
			addChild( goBtn ); 
			addChild( options ); 
			addChild( pagesPane ); 
			 
			searchLabel.x = SPACING; 
			searchTxt.x = searchLabel.x + searchLabel.width + SPACING; 
			goBtn.x = searchTxt.x + searchTxt.width + SPACING; 
			options.x = goBtn.x + goBtn.width + SPACING; 
			pagesPane.x = options.x + options.width + SPACING; 
			searchLabel.y = searchTxt.y = goBtn.y = options.y = SPACING; 
			pagesPane.y = 4; 
			 
			stage.addEventListener( "resize", positionContents ); 
			positionContents(); 
			 
			var loader:MovieLoader = new MovieLoader( "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json" ); 
			loader.addEventListener( "complete", onResults ); 
            loader.addEventListener( "ioError", onIOError ); 
			loader.addEventListener( "apiError", onAPIError ); 
			loader.limit = 1; 
			loader.pageLimit = 6; 
			loader.load(); 
		} 
		 
		private function positionWindows():void { 
			 
			var c:int = 0; 
			var r:int = 0; 
			for ( var i:int = 0; i < windows.length; i++ ) { 
				 
				var window:MovieWindow = windows[ i ] as MovieWindow; 
				 
				if ( maximized != window ) { 
					 
					if( c * ( window.width + SPACING ) > stage.stageWidth - ( window.width / 4 ) - SPACING ) { 
						 
						c = 0; 
						r++; 
					}  
					 
					window.x = c * ( window.width + SPACING ) + SPACING; 
					window.y = r * ( window.height + SPACING ) + SPACING; 
					c++; 
				} 
			} 
		} 
		 
		private function destroyWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				if ( window.parent ) window.parent.removeChild( window ); 
			} 
			 
			windows = []; 
		} 
		 
		private function addWindow( window:MovieWindow ):void { 
			 
			if ( windows.indexOf( window ) == -1 ) { 
				 
				windows.push( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function maximizeWindow( window:MovieWindow ):void { 
			 
			if ( maximized != window ) { 
				 
				if ( maximized ) { 
					 
					restoreWindow( maximized ); 
				} 
				 
				window.minimized = false; 
				window.hasMinimizeButton = false; 
				maximized = window; 
				addChild( window ); 
				window.poster.width *= 2; 
				window.poster.height *= 2; 
				window.displayAll(); 
				window.draggable = false; 
				window.hasCloseButton = true; 
				positionContents(); 
				window.titleBar.getChildAt( 2 ).addEventListener( "click", restore ); 
			} 
			 
			function restore( e:MouseEvent ):void { 
				 
				window.titleBar.getChildAt( 2 ).removeEventListener( "click", restore ); 
				restoreWindow( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximized = null; 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function restoreWindow( window:MovieWindow ):void { 
			 
			window.minimized = false; 
			window.hasMinimizeButton = true; 
			window.hasCloseButton = false; 
			window.draggable = true; 
			window.poster.width /= 2; 
			window.poster.height /= 2; 
			window.width = WINDOW_WIDTH; 
			window.height = WINDOW_HEIGHT; 
			window.displaySimple(); 
			pane.addChild( window ); 
			 
			if ( window == maximized ) { 
				 
				maximized = null; 
			} 
			positionWindows(); 
			//resetWindows(); 
		} 
		 
		private function resetWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				window.width = WINDOW_WIDTH; 
				window.height = WINDOW_HEIGHT; 
				window.minimized = false; 
			} 
			 
			positionWindows(); 
		} 
		 
		private function addPage( isCurrent:Boolean ):void { 
			 
			if ( pages.length < 16 ) { 
				 
				var page:PushButton = new PushButton(); 
				page.label = String( pages.length + 1 ); 
				pages.push( page ); 
				page.width = page.height = 22; 
				page.enabled = ( !isCurrent ); 
				pagesPane.addChild( page ); 
				page.addEventListener( "click", onPageClick ); 
				positionPages(); 
			} 
		} 
		 
		private function positionPages():void { 
			 
			for ( var i:int = 0; i < pages.length; i++ ) { 
				 
				var page:PushButton = pages[ i ]; 
				page.x = ( i * page.width ); 
			} 
			 
			pagesPane.draw(); 
		} 
		 
		private function removePages():void { 
			 
			for each( var btn:PushButton in pages ) { 
				 
				btn.parent.removeChild( btn ); 
			} 
			 
			pages = []; 
		} 
		 
		private function onAdded( e:Event ):void { 
			 
			removeEventListener( e.type, onAdded ); 
			init(); 
		} 
		 
		private function positionContents( e:Event = null ):void { 
			 
			pane.x = 2; 
			pane.y = searchTxt.y + searchTxt.height + SPACING - 2; 
			pane.width = stage.stageWidth - 4; 
			pane.height = stage.stageHeight - pane.y - 2; 
			pagesPane.width = stage.stageWidth - pagesPane.x - 2; 
			positionWindows(); 
			 
			if ( maximized ) { 
				 
				maximized.x = pane.x; 
				maximized.y = pane.y; 
				maximized.width = pane.width; 
				maximized.height = pane.height; 
			} 
		} 
		 
		private function onResults( e:Event ):void { 
			 
			isSearching = false; 
			var loader:MovieLoader = MovieLoader( e.target ); 
			currentLoader = loader; 
			removePages(); 
						 
			if ( loader.movies.length > 0 ) { 
				 
				if ( loader.pageNumber > 1 ) { 
					 
					var i:int = 0; 
					 
					while( i < loader.pageNumber - 1 ) { 
						 
						addPage( false ); 
						i++; 
					} 
				} 
				 
				addPage( true ); 
				 
				while ( pages.length < 16 && pages.length < loader.numOfPages ) { 
					 
					addPage( false ); 
				} 
				 
				for each( var movie:TomatoMovie in loader.movies ) { 
					 
					var window:MovieWindow = new MovieWindow( movie ); 
					pane.addChild( window ); 
					addWindow( window ); 
					window.width = WINDOW_WIDTH; 
					window.height = WINDOW_HEIGHT; 
					window.shadow = true; 
					window.title = movie.title; 
					window.hasMinimizeButton = true; 
					window.enabled = true; 
					 
					positionWindows(); 
				} 
			} 
			else { 
				 
				var w:Window = new Window(); 
				var txt:Text = new Text(); 
				txt.editable = false; 
				txt.text = "no results"; 
				w.title = "Message"; 
				w.content.addChild( txt ); 
				windows.push( w ); 
				addChild( w ); 
				w.x = ( stage.stageWidth - w.width ) / 2; 
				w.y = ( stage.stageHeight - w.height ) / 2; 
			} 
			 
			positionContents(); 
		} 
		 
		private function onIOError( e:IOErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.text; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
		 
		private function onAPIError( e:TomatoLoaderErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.message; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
         
		private function onPageClick( e:MouseEvent ):void { 
			 
			if ( !isSearching ) { 
				 
				isSearching = true; 
				destroyWindows(); 
				var btn:PushButton = e.target as PushButton; 
				currentLoader.pageNumber = uint( btn.label ); 
				currentLoader.clear(); 
				currentLoader.load(); 
			} 
		} 
	} 
}

Below is the result of the following code.


Step 16: Performing Movie Searches

Performing a movie search with the Rotten Tomatoes API is just as simple as loading a movie from the box office. The only additional step is that you must submit the query with the request. Lets create a simple class to make this process easier for us. Create the TomatoMovieSearch class.

 
package { 
	 
	import flash.net.URLVariables; 
				 
	public class TomatoMovieSearch extends MovieLoader { 
		 
		private var _query:String; 
				 
		public static const SOURCE:String = "http://api.rottentomatoes.com//api/public/v1.0/movies.json"; 
			 
		public function TomatoMovieSearch( query:String ) { 
			 
			super( SOURCE ); 
			_query = query; 
		} 
							 
		public override function load():void { 
			 
			if ( !vars ) vars = new URLVariables(); 
			vars.q = _query; 
			super.load(); 
		} 
		 
		public function get query():String { 
			 
			return _query; 
		} 
	} 
}

Now we can perform searches. Try adding an input textfield component and a Go button to your application if you haven't done so already. Again, here's my code.

 
package { 
		 
	import flash.display.DisplayObject; 
	import flash.display.Sprite; 
	import flash.events.Event; 
	import flash.events.MouseEvent; 
	import flash.events.ProgressEvent; 
	import flash.events.IOErrorEvent; 
	import flash.system.Security; 
	import com.bit101.components.*; 
	 
	public class Upcoming extends Sprite { 
			 
		private var searchLabel:Label; 
		private var searchTxt:Text; 
		private var goBtn:PushButton; 
		private var isSearching:Boolean; 
		private var windows:Array; 
		private var windowsMinimized:Boolean; 
		private var maximized:MovieWindow; 
		private var options:ComboBox; 
		private var pane:ScrollPane; 
		private var currentLoader:MovieLoader; 
		private var pages:Array; 
		private var pagesPane:ScrollPane; 
				 
		public static const SPACING:int = 10; 
		public static const WINDOW_WIDTH:int = 300; 
		public static const WINDOW_HEIGHT:int = 200; 
						 
		public function Upcoming() { 
			 
			super(); 
			searchLabel = new Label(); 
			searchLabel.text = "Search: "; 
			searchLabel.width = 40; 
			searchTxt = new Text(); 
			searchTxt.height = 20; 
			goBtn = new PushButton(); 
			goBtn.label = "Go"; 
			goBtn.width = 30; 
			windows = []; 
			options = new ComboBox(); 
			pane = new ScrollPane(); 
			pages = []; 
			pagesPane = new ScrollPane(); 
			pagesPane.height = 30; 
			if ( stage ) init() 
			else addEventListener( "addedToStage", onAdded ); 
		} 
		 
		private function init():void { 
			 
			stage.align = "TL"; 
			stage.scaleMode = "noScale"; 
			 
			addChild( pane ); 
			addChild( searchLabel ); 
			addChild( searchTxt ); 
			addChild( goBtn ); 
			addChild( options ); 
			addChild( pagesPane ); 
			 
			searchLabel.x = SPACING; 
			searchTxt.x = searchLabel.x + searchLabel.width + SPACING; 
			goBtn.x = searchTxt.x + searchTxt.width + SPACING; 
			options.x = goBtn.x + goBtn.width + SPACING; 
			pagesPane.x = options.x + options.width + SPACING; 
			searchLabel.y = searchTxt.y = goBtn.y = options.y = SPACING; 
			pagesPane.y = 4; 
			 
			goBtn.addEventListener( "click", search ); 
			stage.addEventListener( "resize", positionContents ); 
			positionContents(); 
		} 
		 
		private function positionWindows():void { 
			 
			var c:int = 0; 
			var r:int = 0; 
			for ( var i:int = 0; i < windows.length; i++ ) { 
				 
				var window:MovieWindow = windows[ i ] as MovieWindow; 
				 
				if ( maximized != window ) { 
					 
					if( c * ( window.width + SPACING ) > stage.stageWidth - ( window.width / 4 ) - SPACING ) { 
						 
						c = 0; 
						r++; 
					}  
					 
					window.x = c * ( window.width + SPACING ) + SPACING; 
					window.y = r * ( window.height + SPACING ) + SPACING; 
					c++; 
				} 
			} 
		} 
		 
		private function destroyWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				if ( window.parent ) window.parent.removeChild( window ); 
			} 
			 
			windows = []; 
		} 
		 
		private function addWindow( window:MovieWindow ):void { 
			 
			if ( windows.indexOf( window ) == -1 ) { 
				 
				windows.push( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function maximizeWindow( window:MovieWindow ):void { 
			 
			if ( maximized != window ) { 
				 
				if ( maximized ) { 
					 
					restoreWindow( maximized ); 
				} 
				 
				window.minimized = false; 
				window.hasMinimizeButton = false; 
				maximized = window; 
				addChild( window ); 
				window.poster.width *= 2; 
				window.poster.height *= 2; 
				window.displayAll(); 
				window.draggable = false; 
				window.hasCloseButton = true; 
				positionContents(); 
				window.titleBar.getChildAt( 2 ).addEventListener( "click", restore ); 
			} 
			 
			function restore( e:MouseEvent ):void { 
				 
				window.titleBar.getChildAt( 2 ).removeEventListener( "click", restore ); 
				restoreWindow( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximized = null; 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function restoreWindow( window:MovieWindow ):void { 
			 
			window.minimized = false; 
			window.hasMinimizeButton = true; 
			window.hasCloseButton = false; 
			window.draggable = true; 
			window.poster.width /= 2; 
			window.poster.height /= 2; 
			window.width = WINDOW_WIDTH; 
			window.height = WINDOW_HEIGHT; 
			window.displaySimple(); 
			pane.addChild( window ); 
			 
			if ( window == maximized ) { 
				 
				maximized = null; 
			} 
			positionWindows(); 
		} 
		 
		private function resetWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				window.width = WINDOW_WIDTH; 
				window.height = WINDOW_HEIGHT; 
				window.minimized = false; 
			} 
			 
			positionWindows(); 
		} 
		 
		private function addPage( isCurrent:Boolean ):void { 
			 
			if ( pages.length < 16 ) { 
				 
				var page:PushButton = new PushButton(); 
				page.label = String( pages.length + 1 ); 
				pages.push( page ); 
				page.width = page.height = 22; 
				page.enabled = ( !isCurrent ); 
				pagesPane.addChild( page ); 
				page.addEventListener( "click", onPageClick ); 
				positionPages(); 
			} 
		} 
		 
		private function positionPages():void { 
			 
			for ( var i:int = 0; i < pages.length; i++ ) { 
				 
				var page:PushButton = pages[ i ]; 
				page.x = ( i * page.width ); 
			} 
			 
			pagesPane.draw(); 
		} 
		 
		private function removePages():void { 
			 
			for each( var btn:PushButton in pages ) { 
				 
				btn.parent.removeChild( btn ); 
			} 
			 
			pages = []; 
		} 
		 
		private function onAdded( e:Event ):void { 
			 
			removeEventListener( e.type, onAdded ); 
			init(); 
		} 
		 
		private function positionContents( e:Event = null ):void { 
			 
			pane.x = 2; 
			pane.y = searchTxt.y + searchTxt.height + SPACING - 2; 
			pane.width = stage.stageWidth - 4; 
			pane.height = stage.stageHeight - pane.y - 2; 
			pagesPane.width = stage.stageWidth - pagesPane.x - 2; 
			positionWindows(); 
			 
			if ( maximized ) { 
				 
				maximized.x = pane.x; 
				maximized.y = pane.y; 
				maximized.width = pane.width; 
				maximized.height = pane.height; 
			} 
		} 
		 
		private function search( e:MouseEvent ):void { 
			 
			if ( !isSearching && searchTxt.text.length > 0 ) { 
				 
				destroyWindows(); 
				removePages(); 
				isSearching = true; 
				var loader:TomatoMovieSearch = new TomatoMovieSearch( searchTxt.text ); 
				searchTxt.text = ""; 
				loader.addEventListener( "complete", onResults ); 
				loader.addEventListener( "ioError", onIOError ); 
				loader.addEventListener( "apiError", onAPIError ); 
				loader.load(); 
			} 
		} 
		 
		private function onResults( e:Event ):void { 
			 
			isSearching = false; 
			var loader:MovieLoader = MovieLoader( e.target ); 
			currentLoader = loader; 
			removePages(); 
						 
			if ( loader.movies.length > 0 ) { 
				 
				if ( loader.pageNumber > 1 ) { 
					 
					var i:int = 0; 
					 
					while( i < loader.pageNumber - 1 ) { 
						 
						addPage( false ); 
						i++; 
					} 
				} 
				 
				addPage( true ); 
				 
				while ( pages.length < 16 && pages.length < loader.numOfPages ) { 
					 
					addPage( false ); 
				} 
				 
				for each( var movie:TomatoMovie in loader.movies ) { 
					 
					var window:MovieWindow = new MovieWindow( movie ); 
					pane.addChild( window ); 
					addWindow( window ); 
					window.width = WINDOW_WIDTH; 
					window.height = WINDOW_HEIGHT; 
					window.shadow = true; 
					window.title = movie.title; 
					window.hasMinimizeButton = true; 
					window.enabled = true; 
					 
					positionWindows(); 
				} 
			} 
			else { 
				 
				var w:Window = new Window(); 
				var txt:Text = new Text(); 
				txt.editable = false; 
				txt.text = "no results"; 
				w.title = "Message"; 
				w.content.addChild( txt ); 
				windows.push( w ); 
				addChild( w ); 
				w.x = ( stage.stageWidth - w.width ) / 2; 
				w.y = ( stage.stageHeight - w.height ) / 2; 
			} 
			 
			positionContents(); 
		} 
		 
		private function onIOError( e:IOErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.text; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
		 
		private function onAPIError( e:TomatoLoaderErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.message; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
		 
		private function onPageClick( e:MouseEvent ):void { 
			 
			if ( !isSearching ) { 
				 
				isSearching = true; 
				destroyWindows(); 
				var btn:PushButton = e.target as PushButton; 
				currentLoader.pageNumber = uint( btn.label ); 
				currentLoader.clear(); 
				currentLoader.load(); 
			} 
		} 
	} 
}

I added the search event handler method to the application. This method is called when goBtn dispatches the CLICK event. The text from the input textfield is used to search the API with the provided query.


Step 17: Upcoming Movies

Let's create a simple class that will be dedicated to loading Upcoming Movies.

 
package { 
		 
	public class UpcomingMoviesLoader extends MovieLoader { 
		 
		public static const SOURCE:String = "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/upcoming.json"; 
			 
		public function UpcomingMoviesLoader() { 
			 
			super( SOURCE ); 
		} 
	} 
}

Step 18: Top Rentals

Next let's make a class that's dedicated to loading Top Rentals.

 
package { 
			 
	public class TopRentalsLoader extends MovieLoader { 
		 
		public static const SOURCE:String = "http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/top_rentals.json"; 
		 
		public function TopRentalsLoader() { 
			 
			super( SOURCE ); 
		} 
	} 
}

Step 19: Opening Movies

Finally let's make a class that is dedicated to loading Opening Movies.

 
package { 
			 
	public class OpeningMoviesLoader extends MovieLoader { 
		 
		public static const SOURCE:String = "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/opening.json"; 
		 
		public function OpeningMoviesLoader() { 
			 
			super( SOURCE ); 
		} 
	} 
}

Step 20: Displaying Options

Now let's give our users the different options to choose from. Try displaying multiple categories to the user through a combo box, then when the user selects an option, the option that they have selected is loaded and displayed. Here's my code again.

 
package { 
		 
	import flash.display.DisplayObject; 
	import flash.display.Sprite; 
	import flash.events.Event; 
	import flash.events.MouseEvent; 
	import flash.events.ProgressEvent; 
	import flash.events.IOErrorEvent; 
	import flash.system.Security; 
	import com.bit101.components.*; 
	 
	public class Upcoming extends Sprite { 
				 
		private var searchLabel:Label; 
		private var searchTxt:Text; 
		private var goBtn:PushButton; 
		private var isSearching:Boolean; 
		private var windows:Array; 
		private var windowsMinimized:Boolean; 
		private var maximized:MovieWindow; 
		private var options:ComboBox; 
		private var pane:ScrollPane; 
		private var currentLoader:MovieLoader; 
		private var pages:Array; 
		private var pagesPane:ScrollPane; 
				 
		public static const SPACING:int = 10; 
		public static const WINDOW_WIDTH:int = 300; 
		public static const WINDOW_HEIGHT:int = 200; 
						 
		public function Upcoming() { 
			 
			super(); 
			searchLabel = new Label(); 
			searchLabel.text = "Search: "; 
			searchLabel.width = 40; 
			searchTxt = new Text(); 
			searchTxt.height = 20; 
			goBtn = new PushButton(); 
			goBtn.label = "Go"; 
			goBtn.width = 30; 
			windows = []; 
			options = new ComboBox(); 
			pane = new ScrollPane(); 
			pages = []; 
			pagesPane = new ScrollPane(); 
			pagesPane.height = 30; 
			if ( stage ) init() 
			else addEventListener( "addedToStage", onAdded ); 
		} 
		 
		private function init():void { 
			 
			stage.align = "TL"; 
			stage.scaleMode = "noScale"; 
			 
			addChild( pane ); 
			addChild( searchLabel ); 
			addChild( searchTxt ); 
			addChild( goBtn ); 
			addChild( options ); 
			addChild( pagesPane ); 
			 
			searchLabel.x = SPACING; 
			searchTxt.x = searchLabel.x + searchLabel.width + SPACING; 
			goBtn.x = searchTxt.x + searchTxt.width + SPACING; 
			options.x = goBtn.x + goBtn.width + SPACING; 
			pagesPane.x = options.x + options.width + SPACING; 
			searchLabel.y = searchTxt.y = goBtn.y = options.y = SPACING; 
			pagesPane.y = 4; 
			 
			goBtn.addEventListener( "click", search ); 
			stage.addEventListener( "resize", positionContents ); 
			options.addItem( { label:"Opening Movies", value:loadOpeningMovies } ); 
			options.addItem( { label:"Upcoming Movies", value:loadUpcomingMovies } ); 
			options.addItem( { label:"Top Rentals", value:loadTopRentals } ); 
			options.addEventListener( "select", onOptionSelected ); 
			positionContents(); 
		} 
		 
		private function positionWindows():void { 
			 
			var c:int = 0; 
			var r:int = 0; 
			for ( var i:int = 0; i < windows.length; i++ ) { 
				 
				var window:MovieWindow = windows[ i ] as MovieWindow; 
				 
				if ( maximized != window ) { 
					 
					if ( c * ( window.width + SPACING ) > stage.stageWidth - ( window.width / 4 ) - SPACING ) { 
						 
						c = 0; 
						r++; 
					}  
					 
					window.x = c * ( window.width + SPACING ) + SPACING; 
					window.y = r * ( window.height + SPACING ) + SPACING; 
					c++; 
				} 
			} 
		} 
		 
		private function destroyWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				if ( window.parent ) window.parent.removeChild( window ); 
			} 
			 
			windows = []; 
		} 
		 
		private function addWindow( window:MovieWindow ):void { 
			 
			if ( windows.indexOf( window ) == -1 ) { 
				 
				windows.push( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function maximizeWindow( window:MovieWindow ):void { 
			 
			if ( maximized != window ) { 
				 
				if ( maximized ) { 
					 
					restoreWindow( maximized ); 
				} 
				 
				window.minimized = false; 
				window.hasMinimizeButton = false; 
				maximized = window; 
				addChild( window ); 
				window.poster.width *= 2; 
				window.poster.height *= 2; 
				window.displayAll(); 
				window.draggable = false; 
				window.hasCloseButton = true; 
				positionContents(); 
				window.titleBar.getChildAt( 2 ).addEventListener( "click", restore ); 
			} 
			 
			function restore( e:MouseEvent ):void { 
				 
				window.titleBar.getChildAt( 2 ).removeEventListener( "click", restore ); 
				restoreWindow( window ); 
				window.content.addEventListener( "click", onClick ); 
			} 
			 
			function onClick( e:MouseEvent ):void { 
				 
				window.content.removeEventListener( "click", onClick ); 
				maximized = null; 
				maximizeWindow( window ); 
			} 
		} 
		 
		private function restoreWindow( window:MovieWindow ):void { 
			 
			window.minimized = false; 
			window.hasMinimizeButton = true; 
			window.hasCloseButton = false; 
			window.draggable = true; 
			window.poster.width /= 2; 
			window.poster.height /= 2; 
			window.width = WINDOW_WIDTH; 
			window.height = WINDOW_HEIGHT; 
			window.displaySimple(); 
			pane.addChild( window ); 
			 
			if ( window == maximized ) { 
				 
				maximized = null; 
			} 
			positionWindows(); 
		} 
		 
		private function resetWindows():void { 
			 
			for each( var window:Window in windows ) { 
				 
				window.width = WINDOW_WIDTH; 
				window.height = WINDOW_HEIGHT; 
				window.minimized = false; 
			} 
			 
			positionWindows(); 
		} 
		 
		private function loadOpeningMovies():void { 
			 
			var loader:OpeningMoviesLoader = new OpeningMoviesLoader(); 
			loader.addEventListener( "complete", onResults ); 
			loader.addEventListener( "ioError", onIOError ); 
			loader.addEventListener( "apiError", onAPIError ); 
			loader.load(); 
			destroyWindows(); 
			removePages(); 
		} 
		 
		private function loadUpcomingMovies():void { 
			 
			var loader:UpcomingMoviesLoader = new UpcomingMoviesLoader(); 
			loader.addEventListener( "complete", onResults ); 
			loader.addEventListener( "ioError", onIOError ); 
			loader.addEventListener( "apiError", onAPIError ); 
			loader.load(); 
			destroyWindows(); 
			removePages(); 
		} 
		 
		private function loadTopRentals():void { 
			 
			var loader:TopRentalsLoader = new TopRentalsLoader(); 
			loader.addEventListener( "complete", onResults ); 
			loader.addEventListener( "ioError", onIOError ); 
			loader.addEventListener( "apiError", onAPIError ); 
			loader.load(); 
			destroyWindows(); 
			removePages(); 
		} 
		 
		private function addPage( isCurrent:Boolean ):void { 
			 
			if ( pages.length < 16 ) { 
				 
				var page:PushButton = new PushButton(); 
				page.label = String( pages.length + 1 ); 
				pages.push( page ); 
				page.width = page.height = 22; 
				page.enabled = ( !isCurrent ); 
				pagesPane.addChild( page ); 
				page.addEventListener( "click", onPageClick ); 
				positionPages(); 
			} 
		} 
		 
		private function positionPages():void { 
			 
			for ( var i:int = 0; i < pages.length; i++ ) { 
				 
				var page:PushButton = pages[ i ]; 
				page.x = ( i * page.width ); 
			} 
			 
			pagesPane.draw(); 
		} 
		 
		private function removePages():void { 
			 
			for each( var btn:PushButton in pages ) { 
				 
				btn.parent.removeChild( btn ); 
			} 
			 
			pages = []; 
		} 
		 
		private function onAdded( e:Event ):void { 
			 
			removeEventListener( e.type, onAdded ); 
			init(); 
		} 
		 
		private function positionContents( e:Event = null ):void { 
			 
			pane.x = 2; 
			pane.y = searchTxt.y + searchTxt.height + SPACING - 2; 
			pane.width = stage.stageWidth - 4; 
			pane.height = stage.stageHeight - pane.y - 2; 
			pagesPane.width = stage.stageWidth - pagesPane.x - 2; 
			positionWindows(); 
			 
			if ( maximized ) { 
				 
				maximized.x = pane.x; 
				maximized.y = pane.y; 
				maximized.width = pane.width; 
				maximized.height = pane.height; 
			} 
		} 
		 
		private function search( e:MouseEvent ):void { 
			 
			if ( !isSearching && searchTxt.text.length > 0 ) { 
				 
				destroyWindows(); 
				removePages(); 
				isSearching = true; 
				var loader:TomatoMovieSearch = new TomatoMovieSearch( searchTxt.text ); 
				searchTxt.text = ""; 
				loader.addEventListener( "complete", onResults ); 
				loader.addEventListener( "ioError", onIOError ); 
				loader.addEventListener( "apiError", onAPIError ); 
				loader.load(); 
			} 
		} 
		 
		private function onResults( e:Event ):void { 
			 
			isSearching = false; 
			var loader:MovieLoader = MovieLoader( e.target ); 
			currentLoader = loader; 
			removePages(); 
						 
			if ( loader.movies.length > 0 ) { 
				 
				if ( loader.pageNumber > 1 ) { 
					 
					var i:int = 0; 
					 
					while( i < loader.pageNumber - 1 ) { 
						 
						addPage( false ); 
						i++; 
					} 
				} 
				 
				addPage( true ); 
				 
				while ( pages.length < 16 && pages.length < loader.numOfPages ) { 
					 
					addPage( false ); 
				} 
				 
				for each( var movie:TomatoMovie in loader.movies ) { 
					 
					var window:MovieWindow = new MovieWindow( movie ); 
					pane.addChild( window ); 
					addWindow( window ); 
					window.width = WINDOW_WIDTH; 
					window.height = WINDOW_HEIGHT; 
					window.shadow = true; 
					window.title = movie.title; 
					window.hasMinimizeButton = true; 
					window.enabled = true; 
					 
					positionWindows(); 
				} 
			} 
			else { 
				 
				var w:Window = new Window(); 
				var txt:Text = new Text(); 
				txt.editable = false; 
				txt.text = "no results"; 
				w.title = "Message"; 
				w.content.addChild( txt ); 
				windows.push( w ); 
				addChild( w ); 
				w.x = ( stage.stageWidth - w.width ) / 2; 
				w.y = ( stage.stageHeight - w.height ) / 2; 
			} 
			 
			positionContents(); 
		} 
		 
		private function onIOError( e:IOErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.text; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
		 
		private function onAPIError( e:TomatoLoaderErrorEvent ):void { 
			 
			isSearching = false; 
			destroyWindows(); 
			var w:Window = new Window(); 
			var txt:TextArea = new TextArea(); 
			txt.editable = false; 
			txt.text = e.message; 
			w.title = "Error"; 
			w.content.addChild( txt ); 
			windows.push( w ); 
			addChild( w ); 
			w.x = ( stage.stageWidth - w.width ) / 2; 
			w.y = ( stage.stageHeight - w.height ) / 2; 
		} 
		 
		private function onOptionSelected( e:Event ):void { 
			 
			options.selectedItem.value(); 
		} 
		 
		private function onPageClick( e:MouseEvent ):void { 
			 
			if ( !isSearching ) { 
				 
				isSearching = true; 
				destroyWindows(); 
				var btn:PushButton = e.target as PushButton; 
				currentLoader.pageNumber = uint( btn.label ); 
				currentLoader.clear(); 
				currentLoader.load(); 
			} 
		} 
	} 
}

Each option's value is a private method that corresponds to the method that should be loaded from the API.

You can now see options with the combo box.


Step 21: Additional Options

You can follow the steps above to make more options. Try adding an In Theaters option, or an Upcoming DVDs option.


Conclusion

The DataLoader class can also be used with other APIs such as the Facebook Graph API and the Twitter API. You can also continue to extend the TomatoLoader class to expand our library to load more data. Try making classes that implement other methods in the Rotten Tomatoes API. I hope you learned a lot from this tutorial. See you next time.

Advertisement