Build A Web 2.0 Voting Widget With Flex: Part II

Building practical Flash widgets, like ones that record users votes and display the results, is easy in Flex. In my previous article I demonstrated how to set up Flex Builder and create vote tally widgets. In this second article, I’ll show you how we can upgrade that widget to not only read votes, but post them as well. And I’ll demonstrate how to configure Flex projects to build lightweight Flash applications, which are perfect for deployment as widgets.

Download the code archive for this article if you’d like to play along at home.

Pay attention! Once again, there will be a quiz at the end of the article. The first 100 people to complete the quiz will receive a copy of my book, Getting Started With Flex 3, for FREE, delivered to your door. For a limited time, the book is also available as a FREE PDF, thanks to Adobe.

Building the Voting Service

Upgrading the widget from one that reads vote tallies to one that can post votes means upgrading the server backend. To do this, we’ll create a very simple MySQL database that stores the vote topics and the options for each voting topic along with their vote count.

This is the MySQL code we use to create the tables and to preload it with a question:

DROP TABLE IF EXISTS topics; 
CREATE TABLE topics (
 id INT NOT NULL AUTO_INCREMENT,
 text VARCHAR( 255 ) NOT NULL,
 PRIMARY KEY ( id ) );

DROP TABLE IF EXISTS options;
CREATE TABLE options (
 id INT NOT NULL AUTO_INCREMENT,
 topic_id INT NOT NULL,
 text VARCHAR( 255 ),
 votes INT NOT NULL,
 PRIMARY KEY ( id ) );

INSERT INTO topics VALUES ( 0, 'Which is the best Star Wars movie?' );
INSERT INTO options VALUES ( 0, 1, 'Episode I', 10 );
INSERT INTO options VALUES ( 0, 1, 'Episode II', 20 );
INSERT INTO options VALUES ( 0, 1, 'Episode III', 50 );
INSERT INTO options VALUES ( 0, 1, 'Episode IV', 150 );
INSERT INTO options VALUES ( 0, 1, 'Episode V', 250 );
INSERT INTO options VALUES ( 0, 1, 'Episode VI', 30 );

It’s necessary to use mysqladmin to create the database, like so:

% mysqladmin --user=root create votes

… and then load the schema into it using the mysql command:

% mysql --user=root votes < vote.sql

If you’re using PHPMyAdmin to manage your database, you can create the database using PHPMyAdmin, and then use the database restore function to load these tables into the database.

The next step is to upgrade the AMFPHP service code for the vote system. The complete new code is shown here:

<?php 
require_once("MDB2.php");
include_once(AMFPHP_BASE . "shared/util/MethodTable.php");
class VoteService2
{
 function getTopics()
 {
   $dsn = 'mysql://root@localhost/votes';
   $mdb2 =& MDB2::factory($dsn);
   $sth =& $mdb2->prepare( "SELECT * FROM topics" );
   $res = $sth->execute();
   $rows = array();
   while ($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC)) { $rows []= $row; }          
   return $rows;
 }
 function getOptions( $topic )
 {
   $dsn = 'mysql://root@localhost/votes';
   $mdb2 =& MDB2::factory($dsn);
   $sth =& $mdb2->prepare( "SELECT * FROM options WHERE topic_id=?" );
   $res = $sth->execute( $topic );
   $rows = array();
   while ($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC)) { $rows []= $row; }          
   return $rows;
 }
 function addVote( $option )
 {
   $dsn = 'mysql://root@localhost/votes';
   $mdb2 =& MDB2::factory($dsn);
   $sth =& $mdb2->prepare( "SELECT * FROM options WHERE id=?" );
   $res = $sth->execute( $option );
   $rows = array();
   while ($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC)) {  
     $sth2 =& $mdb2->prepare( "UPDATE options SET votes=? WHERE id=?" );
     $row['votes'] = $row['votes'] + 1;
     $res = $sth2->execute( array( $row['votes'], $option ) );
     return $row;
   }
   return null;
 }
}

I won’t get into the weeds on this code, because it’s beyond the focus of this article, but I will point out that the script uses the MDB2 extension to access the MySQL database for the votes. The MDB2 library is the new standard for database-agnostic access with PHP.

The three methods are getTopics, which returns the list of available vote topics, then getOptions, which returns an array of options for the specified topic. Each option includes the option text and the count of votes. Finally, addVote adds a single vote to a particular option.

To test these functions, let’s bring up the AMF service browser that comes with the AMFPHP project and use it to inspect the service, as shown here.

Testing the getTopics function in the AMF votes service

This screenshot shows what happens when we run the getTopics function. It returns an array of objects, each of which has the id of the topic and the text of the topic.

Next we can check the getOptions function, as shown.

Testing the getOptions function

We see that this function returns another array of objects where each object has the text of the option, the unique id of the option, the associated topic’s id, and the current count of votes.

The final service function to test is addVote. Here it is in action.

Testing the addVote function

In this screenshot we specify that we’re adding a vote for the option with the id of 1, which is “Episode I” (seriously). The return is a single object with the new values for the option record with the id of 1.

That’s about all we need concern ourselves with for the web server side. Now comes the fun part: wrapping our application in a nice Flex interface.

Building the Voting Interface

The first step in building the voting application is to get the list of topics and to pick one to vote on. The code below implements just that portion of the functionality, but it defines the entire web server API in the RemoteObject object:

<?xml version="1.0" encoding="utf-8"?>  
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"  
 creationComplete="voteRO.getTopics.send()">  
<mx:Script>  
<![CDATA[  
private var topicID:int = 0;  
private function onGetTopics() : void {  
 var topicind:int = Math.round( Math.random() * voteRO.getTopics.lastResult.length );  
 if ( topicind >= voteRO.getTopics.lastResult.length )  
   topicind = voteRO.getTopics.lastResult.length - 1;  
 var topic:Object = voteRO.getTopics.lastResult[ topicind ];  
 topicID = topic.id;  
 topicText.text = topic.text;  
}  
]]>  
</mx:Script>  
<mx:RemoteObject id="voteRO"  
 endpoint="http://localhost/amfphp/gateway.php"  
 source="votes2.VoteService2" destination="votes2.VoteService2"  
 showBusyCursor="true">  
<mx:method name="getTopics" result="onGetTopics()" />  
<mx:method name="getOptions">  
 <mx:arguments><mx:Topic /></mx:arguments>  
</mx:method>  
<mx:method name="addVote">  
 <mx:arguments><mx:Topic /></mx:arguments>  
</mx:method>  
</mx:RemoteObject>  
 
<mx:Label fontSize="20" id="topicText" />  
 
</mx:Application>

There’s a lot here that we’ve seen from the original examples in the first article, but it does contain a lot more ActionScript code than any of the previous examples. Let’s start with what’s familiar.

The <mx:RemoteObject> is exactly the same as the one we had in the first article, with the exception that I’ve added a few more methods, and two of those methods have arguments. The <mx:Label> tag at the bottom will present the text of the question.

Now, the ActionScript code may look daunting, but it’s relatively straightforward. The onGetTopics method is called in response to a successful call to the getTopics function on the web server. The method takes the topics that were returned and picks one at random using the Math.random method. The code then sets the text of the table to the text of the topic.

The relationship between the getTopics method on the RemoteObject and the onGetTopics method in the application is worth our attention. Web services calls in Flex are asynchronous. In fact, just about everything in Flex is asynchronous and event-based. When you call the send method on getTopics, it returns immediately. Flex then asynchronously makes the call to the web server, gathers the response, and fires the result method which, in this case, calls the onGetTopics method. There’s also a callback for a web services fault, which I don’t specify in this example.

Back to the example, though. Running this should result in something that looks like this.

Checking to make sure we pick a topic correctly

If you have added more topics to the database, you should see one of those topics chosen at random. The data set that I put in the MySQL code just had one topic, and we see that in the figure above.

Now, the next trick is for us to build an interface that allows the user to select one of the options. A conventional interface would consist of a set of radio buttons followed by a Submit button. But for a casual voting system like this, it may be just as easy to present a bunch of buttons, where clicking on one registers a vote. We’ll take that approach here.

The MXML code below, all of which is additional to the previous example, dynamically creates buttons for each of the options and adds them to the Application’s container:

<?xml version="1.0" encoding="utf-8"?>  
<mx:Application ...>  
<mx:Script>  
<![CDATA[  
import mx.controls.Alert;  
import mx.controls.Button;  
 
private var topicID:int = 0;  
 
private function onGetTopics() : void {  
 // ...  
 voteRO.getOptions.send( topicID );  
}  
private function onGetOptions() : void {  
 for each( var opt:Object in voteRO.getOptions.lastResult ) {  
   var optBut:Button = new Button();  
   optBut.label = opt.text;  
   optBut.data = opt.id;  
   optBut.percentWidth = 100;  
   optBut.addEventListener( MouseEvent.CLICK, onOptionButtonClick );  
   addChild( optBut );  
 }  
}  
private function onOptionButtonClick( event:Event ) : void {  
 voteRO.addVote.send( event.currentTarget.data );  
 mx.controls.Alert.show( 'You successfully voted for ' + event.currentTarget.label );  
}  
]]>  
</mx:Script>  
<mx:RemoteObject id="voteRO" ...>  
...  
</mx:RemoteObject>  
 
<mx:Label fontSize="20" id="topicText" />  
 
</mx:Application>

Our original code remains unchanged, except for the addition of the call to getOptions in the onGetTopics method. When getOptions is finished successfully it calls onGetOptions, which creates a Button class for each of the options and adds it to the Application’s container. It also registers the onOptionButtonClick method to respond to mouse clicks on the newly created button.

These two methods, addEventListener and addChild, are fundamental to Flex. Almost every object in the Flex framework posts messages when it changes state. To listen for these messages, call addEventListener and specify the message for which you want to listen, and the method to call when that message is fired.

NOTE: Use the EventDispatcher to Publish Events
The messaging model discussed above is implemented by the EventDispatcher base class, and uses the dispatchEvent method. If you wish to create an object that publishes events, you’ll need to ensure that your object inherits from the EventDispatcher class to do so.

The addChild method is the cornerstone method of the Flex framework’s visual hierarchy. Just like an HTML document, the Flex application’s visual hierarchy can be represented by a tree. The root container is almost always the Application object. Within that, you can have containers and controls and so on, and in turn, almost every container or control supports child controls or containers. You can add child controls (as I’ve done in the code above) using addChild, and you can remove children with the removeChild and removeAllChildren methods.

Okay, with that mini-primer on Flex out of the way, let’s take a look at what happens when we launch this code from Flex Builder – check out this figure.

The dynamically generated option buttons

The figure shows that the application picked a question and put up buttons for each of the different options. The user can then respond to the poll by clicking one of the buttons, as demonstrated in this next figure.

The

Clicking one of these buttons calls the onOptionButtonClick, which in turn calls the addVote function on the server. The application also displays a nifty message box to inform the voter that the vote has been counted.

The final change I’ll make is to display the pie chart of the results of the poll instead of just the alert box. This final version of the code is shown here:

<?xml version="1.0" encoding="utf-8"?>   
<mx:Application ...>  
<mx:Script>  
<![CDATA[  
import mx.controls.Button;  
 
private var topicID:int = 0;  
private var voted:Boolean = false;  
 
private function onGetTopics() : void {  
// ...  
}  
private function onGetOptions() : void {  
 if ( voted )  
   resultPie.dataProvider = voteRO.getOptions.lastResult;  
 else {  
   for each( var opt:Object in voteRO.getOptions.lastResult ) {  
     var optBut:Button = new Button();  
     optBut.label = opt.text;  
     optBut.data = opt.id;  
     optBut.percentWidth = 100;  
     optBut.addEventListener( MouseEvent.CLICK, onOptionButtonClick );  
     optionsBox.addChild( optBut );  
   }  
 }  
}  
private function onOptionButtonClick( event:Event ) : void {  
 voted = true;  
 voteRO.addVote.send( event.currentTarget.data );  
}  
private function onVoteResult() : void {  
 contentStack.selectedChild = pieBox;  
 voteRO.getOptions.send( topicID );  
}  
]]>  
</mx:Script>  
<mx:RemoteObject ...>  
...  
</mx:RemoteObject>  
 
<mx:Label fontSize="20" id="topicText" />  
 
<mx:ViewStack id="contentStack" creationPolicy="all" width="100%" height="100%">  
 <mx:VBox width="100%" height="100%" id="optionsBox">  
 </mx:VBox>  
 <mx:VBox width="100%" height="100%" id="pieBox">  
 <mx:PieChart width="100%" height="100%" id="resultPie" showAllDataTips="true">  
   <mx:series>  
     <mx:PieSeries field="votes" nameField="text" explodeRadius="0.1" />  
   </mx:series>  
 </mx:PieChart>  
 </mx:VBox>  
</mx:ViewStack>  
 
</mx:Application>

The big change here comes at the end of the file where I’ve added a <mx:ViewStack> object that contains two <mx:VBox> containers. A ViewStack is a lot like a deck of cards. You can pick one card to be visible on top, while all the rest remain hidden below. In this case the first VBox, the optionsBox, will hold the buttons for the options. The second VBox, the pieBox, will hold the PieChart. The PieChart is exactly like the one from the first article.

The onGetOptions method in the ActionScript has also changed. It now checks to see if we have voted or not. If we have voted, the code sets the dataProvider on the PieChart to the updated response from getOptions. If we have not voted, it adds the buttons to the optionsBox using addChild.

The onVoteResult method, where we used to put up the alert box, now simply flips the ViewStack card over to the pieBox to present the pie chart. The pie chart updates once the onGetOptions method is fired again.

When we bring this up in the browser, and proceed to vote, we can see the resulting pie chart.

The voting results pie chart

Not bad! We now have a widget that talks to a web service, picks a question at random, presents the question, then displays the voting results using a nifty pie chart if the user chooses to vote.

The only problem is that the Flash application itself is fairly heavy. In fact, on my machine at the time of this writing the SWF files are about 350–400K. That’s a little too heavy for a lightweight widget. What to do?

Moving to Runtime Shared Libraries

Thankfully, Adobe has a fix for the fat SWF problem – and it doesn’t even require any coding changes. All you have to do is tweak your project a little. The trick is to use Runtime Shared Libraries.

The idea is simple. When you compile your SWF file instead of including the library in the SWF itself, you tell the Flash player to go fetch the library at a specific URL. The Flash player then downloads the library automatically for you and caches it, so the next time it looks for the library it will just use this stored version.

To make the change, right click on the “voter” project and select “Properties.” Then select “Flex Build Path” from the left-hand column. The result is shown below.

The Library Path tab in the project configuration

From there, select the framework.swc file as shown here.

The framework link options

Double-click on the Link Type. This will bring up a dialog where you can uncheck the “Use same linkage as framework” option and select “Runtime shared library (RSL)” from the dropdown, as shown.

Setting the linkage to a runtime shared library (RSL)

The final step is to click on the Add button. This brings up the dialog shown below.

Specifying the location of the framework swz file on your site

Now, we enter the URL where the swz file for the framework will be located. This file must be copied from the Flex Builder installation directory to your web server.

Because this particular example uses the charting classes, we’ll also follow the same process with the datavisualization.swc library.

Where to Go from Here

These two articles should set you up well to get started with with Flex. Small applications, such as this voting application, are the perfect place from which to branch out.

If I had to define the three greatest benefits of Flex, it would be these:

  • Flex makes building Flash applications easy for developers.
  • Flash applications run well practically anywhere, and work just as well as their Ajax counterparts.
  • Flex 3 can be used to build Flash applications small enough to create Web 2.0 widgets.

I hope these two articles will leave you pointed in that same direction (be sure to download the code archive for this article).

Quiz Yourself!

Test your understanding of this article with a short quiz – if you’re quick, you’ll receive a FREE copy of my book, Getting Started With Flex 3 for a limited time, thanks to Adobe.

Take the quiz!

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

No Reader comments

Comments on this post are closed.