Advertisement

Design a Panoramio Thumbnail Viewer in Flex

by

During this tutorial I'll take you through the ways in which Flex customizes interface controls. Specifically, we'll be looking at the TileList element, styling it with Flex's GUI toolkit.

To pull this all together, we'll make use of the Panoramio API and build a dynamic thumbnail image viewer.

Quick Preview

Here's the thumbnail viewer we're working towards:

Introduction

Since the dawn of graphical operating systems there have been a handful of standard user interface controls like buttons, labels, text boxes and lists combo boxes. These controls have served their jobs well, but while Vista, Windows 7, MacOS, KDE and Gnome are bringing us fresh new visual designs, developers are quite often stuck designing their own applications with controls that haven’t changed their basic function in 20 years.

It doesn’t matter if you are making a web page, programming a Windows application with VB.Net or creating a RIA with Flex; when you get down to it a list is still a list, and a button is still a button.

Thankfully Flex offers developers a convenient way to customise the humble list, allowing for much more interesting designs than the standard lines of text. We will use this functionality to create a Panoramio image viewer.

Step 1: Create a New Flex Project

Create a new Flex project. Because we will be pulling photos from Panoramio (which returns a JSON object) we need to reference the AS3CoreLib library (which can convert JSON text to an ActionScript object), which you can download from here. Extract the archive and then add a reference to it in the "Flex Build Path".

Step 2: Define States

Unlike desktop applications, Flex applications can only have the one window. States are used to easily change the controls on the screen when more than one display, like a thumbnail view and a detail view, is needed.

New states can be added using the "New State" button in the States toolbox.

We want to add two states. The first will be called "Thumbnails". Click the "New State" button and fill out the dialog like the screenshot below. Notice that we tick "Set as start state", which tells Flex that this is the state that will be first displayed.

We also need a state called "Detail". Again, click the "New State" button and fill out the dialog like the screenshot below.

Step 3: Design the Thumbnail State

Select the "Thumbnails" state from the States toolbox.

Now paint a new TileList component onto the GUI. This component will be used to display the photo thumbnails.

Assign the TileList an ID of "thumbnails" and set the "Data Provider" to {images}. Flex allows you to write ActionScript code within MXML element attributes by enclosing the code in curly braces, and we use that functionality here to set the images collection (which we will create later) as the source of the data for the TileList.

Step 4: Design the Detail State

Select the "Detail" state from the States toolbox.

Paint an Image control, with an id of "photo", that takes up most of the screen space. Then down at the bottom add two Labels, with IDs of "photoTitle" and "photoLocation", and a Button with a label of "Back".

We also need to set the "On click" property of the Button to {this.currentState = 'Thumbnails'}. Changing the value assigned to the "currentState" property is how we change between states in Flex, and just like with the "Data Provider" property of the TileList in step 3, we embed some ActionScript code into the attribute by enclosing it in curly braces. By assigning the string "Thumbnails" to the "currentState" property, we are instructing Flex to drop back to the Thumbnails state.

Your final interface for the Detail state should look like the screenshot below.

Step 5: Create a New MXML Component

MXML Components can be used to define how individual tiles are displayed within the TileList component. Click File > New > MXML Component.

You will now see a dialog that allows you to specify the details for the MXML Component. Fill it out like the screenshot below.

When you are finished you will be taken to a design screen where you can paint components just like you would with the main Flex application.

Step 6: Tile Layout

This MXML Component will be used to draw the individual photos within the TileList. As you can see you have almost unlimited freedom in how the tiles are designed. Each tile can be designed with any Flex control in any layout you like.

The freedom to design each tile individually is what makes the TileList, and other controls like "DataGrid", "HorizontalList", "List", "Menu" and "Tree" so powerful. In fact I have never seen any other GUI toolkit give developers this level of customisation without having to develop a new control from the ground up.

For our application we will display two components on each tile: an Image and a Label. The final layout should look something like the screenshot below.

Step 7: Label

Set the Text property for the Label to {data.photo_title.length > 30 ? data.photo_title.substr(0, 27) + '...' : data.photo_title}. Here we display the "photo_title" property of the object that is bound to the component (we will see how this binding is done later) using a ternary operator (basically a shorthand if..else statement).

If it is 30 characters or less the full string will be displayed. Otherwise we display the first 27 characters followed by 3 periods to indicate that the title has been shortened.

Step 8: Image

Set the "Source" property of the Image to {data.photo_file_url}. Again we are using ActionScript to pull out the "photo_file_url" property of the object that is bound to the component.

Step 9: Manually Specify MXML Attributes

Go back to the main MXML file and switch the editor to the Source view.

You should see a states element added to the Application element. This is the markup that represents the states we defined in step 2.

Under the states element there should be two State elements, and under each of them there will be AddChild elements. These relate to the components that we added to each state.

Under the State element with the "name" of "Thumbnails" you should see an AddChild element that contains a TileList element. This is the TileList that we added in step 3. We are going to have to add some the additional attributes below in order for the TileList to display our custom MXML Component.

itemRenderer="ImageThumbnail" itemDoubleClick="photoSelected()" doubleClickEnabled="true"

So the whole TileList element should read:

<mx:TileList itemRenderer="ImageThumbnail" itemDoubleClick="photoSelected()" doubleClickEnabled="true" dataProvider="{images}" id="thumbnails" bottom="0" top="0" left="0" right="0"/>

The "itemRenderer" attribute is the name of the MXML Component we created in step 5.

The "itemDoubleClick" attribute sets the "photoSelected" function to be called when an item is double-clicked.

The "doubleClickEnabled" attribute tells the component to treat two mouse clicks as a single double-click. Without this attribute the component would treat a double click as two single clicks, and the "itemDoubleClick" event would never be triggered.

Step 10: Add a State Transition Effect

Flex allows you to apply an effect when transitioning between one state and the next. We will use this functionality to initially blur the screen when changing between the Thumbnail and Details states, then bring the screen back into focus.

To define state transistion we need to add a "transitions" element as a child of the MXML "Application" element.

<mx:transitions>
<!-- Individual transitions go here -->
</mx:transitions>

Inside the transitions element we will add a Transition element.

<mx:Transition fromState="*" toState="*">
<!-- Transition effects go here -->
</mx:Transition>

The "fromState" and "toState" attributes define the states to which the transition will apply. Here we've used the asterisk as a wildcard, which matches all states, meaning that the transition effect will be applied when moving from any old state to any new state.

Inside the Transition element we define the effect istelf.

<mx:Blur target="{this}" duration="500" blurYFrom="20.0" blurYTo="1.0" blurXFrom="20.0" blurXTo="1.0" />

The "target" attribute defines which component (and its children) will have the blur effect applied. In this case we want all the elements to be blurred, so we set the target to point to "this".

The "duration" attribute defines how long to apply the blur effect for in milliseconds. We have used 500 here, which is half a second.

The "blurYFrom" and "blurXFrom" attributes define how blurry the effect will appear initially. Bigger values here make the initial effect more blurry.

The "blurYTo" and "blurXTo" attributes define how blurry the effect will be at the end of the duration. We want to bring the screen back into sharp focus, so we assign values of 1 to each of the attributes.

Step 11: Retrieve the Images From Panoramio

At this point we have defined our user interface. Now we need to get some images to display.

Add the following attribute to the Application element:

applicationComplete="appComplete()"

This sets the "appComplete" function to be called when the application has initialised.

Add a "Script" element to the MXML Application element. This gives us a place to write the ActionScript code.

<mx:Script>
<![CDATA[

// Code goes here
]]>
</mx:Script>

In the Script element import the following packages.

import mx.collections.ArrayCollection;
import mx.controls.Alert;
import com.adobe.serialization.json.JSON;

Step 12: Cross Domain Policy

By default Flex will not allow you to load resources from an external domain. However, this behaviour can be overridden if the remote domain has a cross domain security policy file (usually crossdomain.XML). Fortunately Panoramio does have just such a policy file, which we load with the following code:

Security.loadPolicyFile("http://www.panoramio.com/crossdomain.xml");

In step 3 we set the "Data Provider" property of the TileList to a collection called "images". It is here that we actually define that collection. By using the "Bindable" tag on the variable we are telling Flex to watch for any changes to the collection, and to display those changes automatically.

[Bindable] private var images:ArrayCollection = null;

Step 13: appComplete Function

Add a function called "appComplete".

private function appComplete():void
{
	var panoramioURL:String = "http://www.panoramio.com/map/get_panoramas.php?order=popularity&set=public&from=0&to=50&minx=-180&miny=-90&maxx=180&maxy=90&size=medium";
	var request:URLRequest = new URLRequest(panoramioURL);
	var loader:URLLoader = new URLLoader(request);
	loader.addEventListener(Event.COMPLETE, loaderComplete);
	loader.addEventListener(
		IOErrorEvent.IO_ERROR, 
		function(event:Event):void
		{
			Alert.show("There was an IO error contacting Panoramio");
		}
	); 
	loader.addEventListener(
		SecurityErrorEvent.SECURITY_ERROR, 
		function(event:Event):void
		{
			Alert.show("There was a security error contacting Panoramio");
		}
	); 			
}

Getting images from Panoramio is actually very easy. Unlike other image-only sites like Flickr, Panoramio doesn't require the developer to get an API or or to use a special API. You simply make an HTTP request to a specially formatted URL (the Panoramio API page has all the details) and use the returned JSON to get access to the images.

In Flex this means creating a "URLRequest" object with the Panoramio URL, then creating a new "URLLoader", passing that request in the constructor. We then need to watch for 3 events from the URLLoader: Event.COMPLETE, IOErrorEvent.IO_ERROR and SecurityErrorEvent.SECURITY_ERROR.

The Event.COMPLETE event indicates that everything went well, and that the Panoramio server has returned the information we need. In this case we call the "loaderComplete" function.

The IOErrorEvent.IO_ERROR and SecurityErrorEvent.SECURITY_ERROR events indicate that something went wrong. In that case we notify the user that we could not contact the Panoramio server and don't go any further.

Step 14: loaderComplete Function

Now add a new function called "loaderComplete".

private function loaderComplete(event:Event):void
{
	var response:URLLoader = URLLoader(event.target);
	var responseData:Object = JSON.decode(response.data);
	
	this.images = new ArrayCollection();
	
	for each (var image:Object in responseData.photos)
	{
		this.images.addItem(image);	
	}
}

Here we take the JSON returned by Panoramio and convert it into an ActionScript object using the AS3CoreLib library from step 1.

One of the tricks when working with web services in knowing what data has actually been actually returned. If you use Firefox there is a great add-on called JSONovich which will format JSON code within the browser. In the screenshot below you can see the formatted JSON that was returned from the Panoramio web service. Notice that the JSON object has a property called "count" and an array called "photos" that contains the objects which in turn contain the details of the individual photos.

So, what we need to do is take each of the objects in this photos array and place them in our images collection. This is just a matter of looping through each of the objects in the photos collection and then calling "addItem" on the images collection.

Because the images collection had the "Bindable" tag, adding these objects into the collection will cause them to be displayed. If you look back to step 5 you will notice that the values for the Label and Image were set to properties from an object called "data". This data object actually references an item in the images collection, and if you look at the screenshot above you can see where the photo_file_url and photo_title properties came from.

Step 15: photoSelected Function

The final function is called "photoSelected".

private function photoSelected():void
{
	this.currentState = "Detail";
	
	var selectedPhoto:Object = this.thumbnails.selectedItem;
	
	this.photo.source = selectedPhoto.photo_file_url;
	this.photoTitle.text = selectedPhoto.photo_title;
	this.photoLocation.text = "Lat: " + selectedPhoto.latitude + " Long: " + selectedPhoto.longitude;
}

This function is called when the user double-clicks on a item in the TileList. Here we set the current state to "Detail", and set the properties of the Image and Labels using the properties of the selected TileList item.

Conclusion

We have only just scratched the surface of what is possible with MXML Components, but hopefully you can see their potential. Being able to customise how a tile is drawn, without having to write any code, is just one of the many features that sets Flex apart from other development platforms.

Advertisement