Friday, May 14, 2010

Flex PopUpMenuButton

I was tasked the other day at work to modify the Flex PopUpMenuButton to behave like a single button. The PopUpMenuButton default basically acts like two buttons. The left button executes the first menu item, and the "Arrow" button drops down the menu. The users did not like this default functionality and only wanted one button to drop down the menu. Instead of trying to re-create the PopUpMenuButton with a single button, I instead extended the PopUpMenuButton. With a simple mx_internal override of one function, I was able to get the menu to drop down no matter where you click on the button.

Here's the example. (right click to view source):


Hopefully, this will save someone some time.

Saturday, May 1, 2010

A Smarter Flex ChannelSet

The ChannelSet class in the Adobe Flex Framework is pretty brilliant. For example, lets say we were building an online auction application using LCDS(LiveCycle Data Services) where users can submit bids on items. We would want this application to be Real-Time. Our first choice for how the client connects to the back-end should be the RTMP protocol using the RTMPChannel class. This type of connection beats out any other connections out there. It beats streaming/comet and polling type connections by a long shot. But lets say some of our users are behind strict firewalls that will not allow them to connect using the RTMP protocol. Does this mean these users will not be able to use our application? Nope, this is why the ChannelSet class is so brilliant. We can provide the client with backup/failover channels to connect on. Take a look at the example below.

   
   
   

Notice above I created a ChannelSet with three channels defined. The client will attempt to connect using the RTMP channel first. If the client fails on RTMP, it will attempt on streaming. If the client fails on streaming, it will attempt on the long polling channel. As a developer this is nice as I don't have to code any of this fail over logic. This functionality is baked right into the ChannelSet class. There is only one problem with the ChannelSet class. The problem arises when a client connects, disconnects and then connects again. The client might get disconnected due to loosing their wireless internet. When the client gets disconnected, the ChannelSet class "connection logic" will kick in gear and attempt to connect by looping over the channels until one channel successfully connects. This might be fine for some situations, but let me explain a particular situation where you don't want this to happen.

For example, a user initially connects using RTMP. The user is using the application for awhile, but all of a sudden they lose their internet connection. The ChannelSet class is made aware of the disconnect and its "connection logic" kicks into gear and loops over each channel attempting to connect until one is successful. The users internet connection than comes back online. The user for some reason is now connected using long polling. What! Why the heck is the user connected using the long polling channel when the user initially connected using the RTMP channel. The answer is because the ChannelSet class "connection logic" is just a simple loop over the channels and when the users internet came back, the loop was currently attempting to connect on the long polling channel and it did. That is why the user is now connected over the long polling channel.

So, basically to sum up this problem. The user initially connected over RTMP, gets disconnected, re-connects and connects over long polling that is on the bottom of our channel list. How do we fix this little problem? Simple, we create a smarter ChannelSet class. Take a look at the smarter ChannelSet class that I named "CustomChannelSet" below.
package net.flashdan.util {
 import mx.messaging.Channel;
 import mx.messaging.ChannelSet;
 import mx.messaging.events.ChannelEvent;
 
 public class CustomChannelSet extends ChannelSet {
  
  public function CustomChannelSet(channelIds:Array=null, clusteredWithURLLoadBalancing:Boolean=false) {
   super(channelIds, clusteredWithURLLoadBalancing);
   addEventListener(ChannelEvent.CONNECT, onChannelConnect);
  }
  
  private function onChannelConnect(event:ChannelEvent):void {
   //loop through the channels for this channelset and remove all but the connected one
   for (var i:int=channels.length-1;i>=0;i--) {
    var channel:Channel = channels[i] as Channel;
    if(channel.id != currentChannel.id){
     removeChannel(channel);
    }
   }
  }
 }
}
We would then use our CustomChannelSet like so.

   
   
   

The solution is pretty simple. When the user initially connects over a particular channel, remove all other channels except the currently connected channel. This way when the user initially connects over RTMP, gets disconnected, re-connects, the ChannelSet "connection logic" only attempts to connect over the RTMP channel and no others. Problem solved! I am providing a small sample flex application with the SmarterChannelSet class being used. It's a Flex 4 project. Unzip the file and import the .fxp project in Flash Builder 4.
Download Source