Archive for the ‘Flex’ Category

The Fading of the Flex Framework

Monday, December 12th, 2011

I do not usually write “controversial” business posts, but I wanted to type out my thoughts after some more information has flowed out from Adobe since last month’s infamous post.

Here are the important new links:

I was surprised the question that got the most votes was how Adobe could restore confidence in the developer community. To me, that is mostly setting oneself up to receive a lot of marketing spin. It’s like asking the waiter if the special is any good. (Hint: they aren’t going to say it is awful or even meh — at worst you’ll get “good, but not my personal favorite.”)

So let’s get into the video. At the 0:01:25 mark…

“We have decided to restructure the company and to focus the priorities in such a way that we don’t feel like we can run Flex the way that we have before where it is completely resourced and funded by Adobe. And as David has alluded to, where we have an ambition to run it as a standalone business that is actually profitable in its own right. And that is one of the things that is fundamental in guiding the change that we have made in deciding to move the project into Apache and into more of a community development model.”

So we find out that Flex is a money loser for Adobe. And this is not a Google + YouTube or Amazon + Kindle Fire money losing kind of thing. Adobe’s business decision is to make cuts and reduce resources. But is this like a skeleton crew? You know, kind of like that Star Trek episode where only Dr. Crusher and Captain Picard were left to run the Enterprise?

Which brings us to the money question — the one I would have put all 10 of my votes behind. It was asked like this:

“How many Adobe engineers will be working for the benefit of FLEX (SDK, compilers, tooling, runtimes, documentation, evangelism etc) after the transfer to the ASF.”

Better still to ask it more direct, like this:

“How many Adobe employed developers will be working fulltime on the Flex SDK code?”

In the link to the video above at 0:15:45, the general question is answered like this:

“We have, moving forward, on things related to Flex, dozens of people still in the company. I mean we don’t disclose exact numbers, because obviously it fluctuates from time to time as we move people around to assignments inside the company, but there definitely are dozens of people that are still engaged in various aspects of our Flex agenda. So whether that be direct contributions to Apache, whether it be honoring our maintenance and support contracts that are associated with any fixes for the Adobe releases of Flex, our evangelism and support organization, documentation.”

“We are continuing to have dedicated staffing across all of those functions and, like I said, that comes out to dozens of people. Still a significant effort. It is less than what it was before these changes. So the point of what I said before, we did make the decision, as part of this transition, that we did not think we had a viable path forward for Flex as a standalone profitable business within the context of our new strategy.”

“And that is why we have made the changes we have made to actually shift to the Apache and the community development model so that we can ensure that there is a successful path forward for Flex with dedicated resourcing and staffing across all of those functions, albeit at a reduced level from what we had committed internally. And it is our sincere hope that, with the community engagement that we expect to enable through Apache, and hopefully contributions from any of you, that we will be able to ensure a thriving and innovative and lively path forward for Flex within the context of Apache.”

Not going to happen.

I’m sorry, I don’t intentionally want to be negative here, but seriously. Who thinks the Flex SDK will thrive rather than just survive over the next 2-4 years. Aren’t most freelance Flex developers like me — meaning the vast majority of your time is spent coding client’s projects (i.e. billing hours). You are invested in Flex so you dutifully file bug reports (good ones too, where you spend an hour boiling it down and submit a small .fxp that illustrates the problem.) Knowing how others have helped your understanding, you feel the desire to give back, but your quality, code-sharing blog posts are less in number than you thought. And when you do find some extra time, you spend it sharpening your secondary skill-set in the event your primary one gets… oh, never mind.

If the 3rd party Flex component market never took off, why does Adobe think there is a large community willing to spend time (which equals money) doing what Adobe is no longer willing to do — fix and further the hot mess that is the Flex SDK?

Perhaps they think that because the Flex architectural framework space (Swiz, Robotlegs, Parsley, etc.) prospered, the same can hold true for the core framework. I readily admit that I don’t fully understand open source, but I get that developers donate time they could be billing because… ummm, betterment of… oh forget it.

But what’s easily observable is the road littered with abandoned code. I gladly pay for code like Greensock’s Tweening Platform. And though my $100 only buys probably 1 to 1.5 hours of coding time, hopefully there are enough of us that it creates a nice profitable business for Jack. With architectural frameworks, I guess there is the thrill and perhaps the business need (like with Universal Mind, or EffectiveUI)? But is there really a thrill in basically working for Adobe but without the monetary compensation, kind of like when they quit giving Milton a paycheck in Office Space. The Flex 4 SDK was largely about Flash Catalyst and it just got discontinued. Yeah, untangling all of that sounds like fun.

So perhaps the other reason is true then – the business need. I have made the most money in my career specializing in Flex development. Should we all donate 8 weeks a year to keep our favorite SDK around? That’s more than $20k of billable hours. I was content paying Adobe $550 every 12 months for my dot 5 Master Collection upgrade believing they would dedicate the resources. That only buys 5-7 hours of development time, however, and apparently that’s not paying Adobe’s bills.

Adobe is exiting the enterprise framework business and, according to the video, does not plan to reenter it with an HTML solution. Which answers the question of why did Adobe make this announcement about Flex before presenting their alternative.

Developers will still make great money building enterprise apps with the Flex framework. Better devote time to sharpening that secondary skill-set during the countdown, however.

A Faster Splash Screen for AIR on iOS

Thursday, September 15th, 2011

I noticed that some AIR apps on my iPad2 show their splash screen instantly, whereas others (Politifact, Neptune) show a blank black screen for 2-3 seconds before their splash screen. The latter are using the Flex framework, although this is not completely a Flex issue, but rather any AIR app that uses a Flash preloader. Interestingly, the excellent Machinarium app shows its “Amanita Designs” splash screen instantly, but then shows a blank black screen for 10 seconds (!) before showing the next splash screen.

A client’s app I am developing is using the Flex framework, so that is what I will be posting about.

In a Flex app, you specify a splash screen image like this:

  1. <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
  2.     xmlns:s="library://ns.adobe.com/flex/spark"
  3.     splashScreenImage="@Embed('assets/splash.png')"
  4.     splashScreenScaleMode="letterbox">

Or, for a more advanced method, you can extend spark.preloaders.SplashScreen as described by Jason San Jose’s article in order to provide a different splash screen based on dpi, orientation, or device.

However, if you want an instant splash screen on iOS, you have to make use of their launch image method. For my client’s iPad app, it meant creating Default-Portrait.png and Default-Landscape.png files as described in this Adobe documentation on iOS launch Images. You place those images directly in the src folder of your Flex mobile application.

So having those launch images results in an instant splash screen, but it will be followed by a blank black screen if you do not also specify a splash screen image in Flex (or have some Flash preloader display a splash screen image, which is what the non-Flex Machinarium app is lacking).

My single splash image was 1280 x 1280 in order to support both iPad and Android in both orientations. I made the Default-Portrait.png 768 x 1004 and the Default-Landscape.png 1004 x 768 as specified in the Adobe documentation above.

After doing this, however, I noticed that my launch image and my splash screen image were not positioned exactly the same. This produced a noticeable jump of 20 pixels when the launch image became the splash image.

When doing a search, I found this documentation that states:

“For an application that is not full-screen, the top 20 pixels of the default image art are ignored. The iPhone displays its status bar over the 20 pixel-wide rectangle at the top of the default image. In a landscape-orientation application, this region corresponds to the left 20 pixel-wide rectangle of the Default.png file (which displays on the top in landscape mode). In a portait-orientation application, this region is the top 20 pixel-wide rectangle of the Default.png file.”

So what I had to do was open my 1280 x 1280 splash screen image, and clip it to 768 x 1024 (not 1004) at point 256, 118. Then save that as my Default-Portrait.png. For the landscape launch image, I clipped it to 1024 x 768 at point 128, 246.

That made it so that the launch image and the splash screen image were positioned exactly the same. The user could not see a difference as the app changed between the two. This removed the 2-3 seconds of having a blank black screen before the splash screen appeared.

Achieving a High Number of Interactive Markers with the Google Maps for Flash API

Thursday, June 2nd, 2011

For the past year, I have been working on a client project that utilized the Google Maps for Flash API. One issue that I hoped to address was the sluggishness that results from instantiating 100+ instances of com.google.map.overlays.Marker. The forums have a few threads discussing this topic. Digging into them, you immediately get the idea to not use Google’s Marker class, but rather a single instance of a custom class you create that extends OverlayBase. This class would then create markers as instances of Sprite. You would override the OverlayBase’s positionOverlay() method to reposition your markers. This technique allowed us to achieve a few hundred interactive markers before the application became noticeably sluggish.

Getting to the next level of supporting 1,000+ interactive markers, however, was a much more difficult task. There was this thread, where Jonathan Wagner described the use of bitmap tiles. He went further by providing an example and sharing his code. Unfortunately, while the example achieved an ultra-high number of markers, the markers were not interactive. However, it certainly pointed me in the right direction.

Basically, what you need to do is create 256 x 256 bitmaps that will exactly overlay the 256 x 256 bitmap tiles that Google serves up for their map.

The 256 x 256 map tile from Google combined with the one you create in ActionScript

When each marker was its own instance of Sprite, you could easily register event listeners for the MouseEvents. However, with a bitmap that can contain several markers, you have to get more creative. More on that below.

The classes are:

  • Marker.as
  • MarkerGroup.as
  • MarkerTileLayer.as
  • Tile.as

Your application creates an instance of MarkerTileLayer. It also creates an instance of com.google.maps.overlays.TileLayerOverlay, passing in the MarkerTileLayer as a parameter.

PerformanceMarkers.mxml

  1.     markerTileLayer = new MarkerTileLayer(map);
  2.     tileLayerOverlay = new TileLayerOverlay(markerTileLayer);
  3.     tileLayerOverlay.addEventListener(MapEvent.TILES_LOADED, tileLayerOverlay_tilesLoadedHandler);
  4.     map.addOverlay(tileLayerOverlay);
  5.     addMarkers();

The TILES_LOADED event listener is important because it is how we keep the number of Tile instances under control by calling markerTileLayer.disposeOffscreenCachedTiles().

In the addMarkers() method, I create instances of Marker. The constructor takes two parameters: the LatLng and BitmapData. I add the event listeners for Marker.MARKER_CLICK and Marker.MARKER_HOVER directly to the Marker instances.

So my application only deals directly with two of the four classes in the above list: MarkerTileLayer and Marker.

MarkerTileLayer deals with the other two classes: MarkerGroup and Tile.

MarkerGroup is pretty simple. It is a group of Marker objects that are contained by a LatLngBounds. Both of these are passed into the MarkerGroup constructor. It then exposes a public method whose signature is containsMarker(marker:Marker):Boolean.

Tile is more complex. Tile extends Sprite and is the DisplayObject that gets returned from MarkerTileLayer.loadTile().

MarkerTileLayer has a setter for the markers Array. This is where I break up the Markers into MarkerGroups. I use MarkerGroups so that the MarkerTileLayer.loadTile() method does not have to cycle through every Marker instance to check if it should be rendered onto that Tile. Instead, it can check a smaller number of MarkerGroups. If the MarkerGroup’s latLngBounds does not intersect the Tile, then the entire MarkerGroup can be skipped.

When a marker is clicked, figuring out which marker is tricky. You just need to register an event listener on the Marker instance for Marker.MARKER_CLICK, but underneath the hood MarkerTileLayer has event listeners on the Tile. When a Tile is clicked, it must determine which Marker. It does this in the method Tile.getTopmostMarkerAtPoint().

One issue I have found is that if a Marker has a pixel with an alpha value of less than 1.0, and that pixel is the one clicked, and the Marker overlaps another Marker, then the Tile.getTopmostMarkerAtPoint() does not return the Marker (and thus the Marker does not dispatch any event).

Screenshot of PerformanceMarkers

I have commented the code pretty well. Here are the application and source code. Be sure to try a larger number of markers than the default 500. On my machine, performance is excellent up to 5,000 and still very good at 10,000 and 20,000 (with only a slight stutter when zooming).

A major challenge is still animated markers. I have a solution to accommodate limited animation (4 frames) which I hope to go over in a future blog post. Basically each Tile has 4 BitmapData objects and swaps them out on the ENTER_FRAME event.

Gotcha on Modifying Objects in a Sorted ArrayCollection

Thursday, October 14th, 2010

I experienced a problem today where iterating over the objects in my ArrayCollection was producing some strange results. I’ve boiled the issue down to the following example:

Book.as

  1. package com.curiousfind.testflex4
  2. {
  3.     [Bindable]
  4.     public class Book
  5.     {
  6.         public var title:String;
  7.         public var price:int;
  8.  
  9.         public function Book(title:String, price:int)
  10.         {
  11.             this.title = title;
  12.             this.price = price;
  13.         }
  14.     }
  15. }

In the application, I setup an ArrayCollection with a sort:

  1. private function setup():void
  2. {
  3.     var array:Array =
  4.     [
  5.         new Book("Professional Adobe Flex 3", 49),
  6.         new Book("Flex 4 in Action", 49),
  7.         new Book("Enterprise Development with Flex", 54),
  8.         new Book("ActionScript 3 Cookbook", 39),
  9.         new Book("Essential ActionScript 3", 54),
  10.         new Book("ActionScript 3 Bible", 49),
  11.         new Book("Advanced ActionScript 3 with Design Patterns", 49)
  12.     ];
  13.     books = new ArrayCollection(array);
  14.     var sort:Sort = new Sort();
  15.     var sortField1:SortField = new SortField("price");
  16.     var sortField2:SortField = new SortField("title");
  17.     sort.fields = [sortField1, sortField2];
  18.     books.sort = sort;
  19.     books.refresh();      
  20. }

Datagrid with the book prices

Now I have a button that will iterate through the books and increase each book’s price by $10

  1. private function button1_clickHandler():void
  2. {
  3.     for each (var book:Book in books)
  4.         book.price = book.price + 10;    
  5. }

However, when button is clicked I get this:

Datagrid showing books with incorrect prices

All the book prices except one are incorrect. So what happened?

The Book class is marked as bindable so that the books ArrayCollection will receive PropertyChangeEvent.PROPERTY_CHANGE whenever one of the properties of the Book object is changed. (That is setup in the startTrackUpdates() method of ArrayList.as if you are looking at the Flex SDK source). The ArrayCollection re-sorts itself when one of the objects it contains dispatches a PropertyChangeEvent. So the for each loop is not going through each item of the ArrayCollection. It hits some Book objects multiple times and some it misses.

There are a couple of solutions. The simplest is this:

  1. private function button2_clickHandler():void
  2. {
  3.     var array:Array = books.toArray();
  4.     for each (var book:Book in array)
  5.         book.price = book.price + 10;    
  6. }

By calling the ArrayCollection.toArray() method I get an array that won’t change on me as I iterate through it.

Another way would be to remove the [Bindable] metadata from the Book class and do this:

  1. private function button1_clickHandler():void
  2. {
  3.     for each (var book:Book in books)
  4.         book.price = book.price + 10;
  5.     books.refresh();
  6. }

Removing the [Bindable] metadata tag means that the compiler does not make the Book class extend EventDispatcher nor rewrite the title and price properties to be getters and setters. Therefore the ArrayCollection will not be notified via PropertyChangeEvent.PROPERTY_CHANGE that the objects it contains have been updated. Since the ArrayCollection won’t be notified, it in turn will not re-sort as we change a Book object’s price. It also will not dispatch CollectionEvent.COLLECTION_CHANGE and the DataGrid will not update. To get the ArrayCollection to re-sort and the DataGrid to update with the new prices, I would have to manually call the ArrayCollection.refresh() method.

Here is the actual Flex application that demonstrates the gotcha and the toArray() method that works:

The Flash plugin is required to view this object.

Introducing Backlit — a Flex CMS using XStandard

Monday, February 2nd, 2009

backlit2

Backlit is the Flex project I have been working on for several months.  It is the content management system that I am offering to distinguish my website hosting business from the typical shared hosting provider.   Backlit makes it easy to manage articles and photos.  It utilizes the XStandard plugin which generates clean XHTML strict code.  Photos can be uploaded, moved between albums, and deleted.

Here are some links that provide a lot more information:

1. Demo Site Backend (username: guest password: password)

2. Tutorials

3. Super Easy to Create a Frontend

4. More Information

The demo site completely resets itself every few hours, so feel free to give it a good test drive.

Passed the Flex 3 with AIR Certification Exam

Friday, January 9th, 2009

I end full time work with my current employer on January 31, so I wanted to get the Flex 3 with AIR certification on my resume. I passed, but it is obvious which section nearly torpedoed me! I was confident I had only missed seven… maybe eight of the fifty questions. I’d love to have been able to review which ones I missed, but since that isn’t an option, I’ll revisit the reference books while it is still fresh.

Adobe Certified Expert - Flex 3 with AIR

Sorting a Tree in Flex and Maintaining Open State on Refresh

Monday, December 8th, 2008

I used Flex’s tree component in a content management system. The tree’s data provider was an ArrayCollection that received its data from a server call. The application would get the site map from the server whenever:

  • a new article was saved
  • an existing article was saved with a new parentID
  • the user clicked the Refresh Site Map button

The site map had two issues: the articles were not sorted alphabetically, and the site map would lose its open state upon refresh.

I was able to solve both issues. Here is an example. You can view the full code.

You will first notice that the articles in the tree are not sorted alphabetically. If you check the Sort checkbox and click Refresh, it will simulate a call to the server where the data provider is replaced. The sort is performed in the setter for the data provider.

Also notice that the tree does not maintain its open state when its data provider is changed. Now check the Remember Open State checkbox and click Refresh. The tree maintains its open state.

The article objects in the tree have two properties: articleID and title. The articleID needs to be unique in order for the Remember Open State to work. It remembers its state by using recursion to cycle through all the tree items and saving an array of articleIDs that are open. When the new data provider is set, the nodes with articleIDs saved in the _openItems array are opened with the tree’s expandItem method.

The Flash plugin is required to view this object.

Creating a Loosely-Coupled Custom Component in Flex

Thursday, November 20th, 2008

Note: This is a tutorial that I wrote for a contest at SitePoint earlier this year.  I had titled it Creating a Reusable Custom Component in Flex. After watching this session from 360Flex, I now know about there being two “levels” of reusability: loosely-coupled components and polished, fit for cataloging/distribution components. This tutorial is for the former.


Creating a Reusable Custom Component in Flex

by Jamie McDaniel

In this tutorial, I will guide you through the process of creating a custom Flex component. You will also learn how to pass data between the main Flex application and the custom component using best practices.

If you do not have Flex Builder, you can download a 60-day trial.  If you are a student, faculty, or staff of an eligible education institution, you can get the education version of Flex Builder 3 free by going to http://flexregistration.com.

The component we will be creating is a slideshow navigation bar.  The navigation bar will include buttons for previous, next, play/pause, full screen on/off, and sound on/off.  We will start by using the default buttons in Flex, and then proceed to style them using more recognizable graphics.

Open Flex Builder and click File -> New -> Flex Project. I am giving this project the name of SitePointTutorial.

When you click Finish, Flex Builder will create folders for your project. In the src folder you will find the main application file called SitePointTutorial.mxml. Go ahead and create three additional folders inside the src folder and name them assets, components, and events.

Click File -> New -> MXML Component. Select the components folder as the parent folder and give this component a filename of SlideshowNavigationBar.  Base it on HBox and set the width to 400 and the height to 60.

In the SlideShowNavigationBar.mxml file, enter the following code:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="60" initialize="onInitialize()" horizontalAlign="center" verticalAlign="middle">
  3.     <mx:Script>
  4.         <![CDATA[
  5.             private function onInitialize():void
  6.             {
  7.                 this.drawRoundRect(0, 0, 400, 60,
  8.                 {tl: 7, tr:7, bl:7, br: 7},
  9.                 0x3A3A3A, 0.75);
  10.             }
  11.         ]]>
  12.     </mx:Script>
  13.     <mx:Button id="btPrev" label="Previous"/>
  14.     <mx:Button id="btNext" label="Next"/>
  15.     <mx:Button id="btPlay" label="Play"/>
  16.     <mx:Button id="btFullScreen" label="Full Screen"/>
  17.     <mx:Button id="btSound" label="Sound"/>
  18. </mx:HBox>

This will create the five buttons and center them within an HBox with a rounded rectangle background. To include our new component in the main application, open up SitePointTutorial.mxml and update it so your code matches the following:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:components="components.*" layout="absolute">
  3.     <mx:HBox width="100%" horizontalAlign="center" bottom="60">
  4.         <components:SlideshowNavigationBar id="mySlideshowNavigationBar"/>
  5.     </mx:HBox>
  6. </mx:Application>

Note the xmlns:components="components.*" in the Application tag. This is because our custom component is in a folder that we named components.  Therefore we need to define a namespace that tells the compiler where to find our custom component.  All the standard components that ship with the Flex framework begin with a namespace of mx.  The mx namespace is also defined in the Application tag and it points to www.adobe.com/2006/mxml.

Our component is added to the application with the simple mxml tag:

  1. <components:SlideshowNavigationBar id="mySlideshowNavigationBar"/>

The id property is needed to access the component with ActionScript.

To test the application, click the Debug button.

You should see the application running in your browser.

Styling the Slideshow Navigation Bar

In SlideshowNavigationBar.mxml add the following code inside the <mx:Script> tag just above the onInitialize function:

  1. [Embed(source="../assets/prev.png")]
  2. private var _prevIcon:Class;
  3. [Embed(source="../assets/next.png")]
  4. private var _nextIcon:Class;
  5. [Embed(source="../assets/play.png")]
  6. private var _playIcon:Class;
  7. [Embed(source="../assets/pause.png")]
  8. private var _pauseIcon:Class;
  9. [Embed(source="../assets/fullscreen.png")]
  10. private var _fullscreenIcon:Class;
  11. [Embed(source="../assets/smallscreen.png")]
  12. private var _smallscreenIcon:Class;
  13. [Embed(source="../assets/sound_on.png")]
  14. private var _soundOnIcon:Class;
  15. [Embed(source="../assets/sound_off.png")]
  16. private var _soundOffIcon:Class;

You will need to copy the graphic files from this tutorial into the assets folder of your project.

It is worth noting that we could do without the above code and set the upSkin, overSkin, and downSkin properties of each of our buttons like so:

  1. <mx:Button id="btPrev" upSkin="@Embed(source='../assets/prev.png')"
  2. overSkin="@Embed(source='../assets/prev.png')"
  3. downSkin="@Embed(source='../assets/prev.png')"/>

However by associating each graphic with a class, we can swap out the icons at runtime using ActionScript. We will need to do that, for example, when the play button should show a pause graphic.

Also note that we have designated the scope of our variables as private and have prefixed their names with an underscore.  Setting variables inside a custom component as private is not required, but it is recommended as a best practice.  You can always make the variable more accessible later if needed. Prefixing private variables with an underscore is a coding practice used by many developers for readability.

We now need to update the onInitialize function so that it contains the following code:

  1. private function onInitialize():void
  2. {
  3.     btPrev.setStyle("upSkin", _prevIcon);
  4.     btPrev.setStyle("overSkin", _prevIcon);
  5.     btPrev.setStyle("downSkin", _prevIcon);
  6.     btNext.setStyle("upSkin", _nextIcon);
  7.     btNext.setStyle("overSkin", _nextIcon);
  8.     btNext.setStyle("downSkin", _nextIcon);
  9.     btPlay.setStyle("upSkin", _pauseIcon);
  10.     btPlay.setStyle("overSkin", _pauseIcon);
  11.     btPlay.setStyle("downSkin", _pauseIcon);
  12.     btFullScreen.setStyle("upSkin", _fullscreenIcon);
  13.     btFullScreen.setStyle("overSkin", _fullscreenIcon);
  14.     btFullScreen.setStyle("downSkin", _fullscreenIcon);
  15.     btSound.setStyle("upSkin", _soundOnIcon);
  16.     btSound.setStyle("overSkin", _soundOnIcon);
  17.     btSound.setStyle("downSkin", _soundOnIcon);
  18.     this.drawRoundRect(0, 0, 400, 60,
  19.     {tl: 7, tr:7, bl:7, br: 7},
  20.     0x3A3A3A, 0.75);
  21. }

Remove the label property on each of the five buttons so that they look like the following:

  1. <mx:Button id="btPrev"/>
  2. <mx:Button id="btNext"/>
  3. <mx:Button id="btPlay"/>
  4. <mx:Button id="btFullScreen"/>
  5. <mx:Button id="btVolume"/>

Run the application and you should see the styled navigation bar below:

Changing a Button’s Appearance on Mouseover

When the user hovers over the buttons, we would like to provide visual feedback.  One way to do that is by adjusting the alpha value.  In the onInitialize function, add the following code:

  1. btPrev.alpha = 0.75;
  2. btNext.alpha = 0.75;
  3. btPlay.alpha = 0.75;
  4. btFullScreen.alpha = 0.75;
  5. btSound.alpha = 0.75;

To make the buttons respond to a mouseover event, we will first need to register event handler functions for each of the five buttons.  To do so, add the following code to the onInitialize function:

  1. btPrev.addEventListener(MouseEvent.MOUSE_OVER, onButtonMouseOver);
  2. btPrev.addEventListener(MouseEvent.MOUSE_OUT, onButtonMouseOut);
  3. btNext.addEventListener(MouseEvent.MOUSE_OVER, onButtonMouseOver);
  4. btNext.addEventListener(MouseEvent.MOUSE_OUT, onButtonMouseOut);
  5. btPlay.addEventListener(MouseEvent.MOUSE_OVER, onButtonMouseOver);
  6. btPlay.addEventListener(MouseEvent.MOUSE_OUT, onButtonMouseOut);
  7. btFullScreen.addEventListener(MouseEvent.MOUSE_OVER, onButtonMouseOver);
  8. btFullScreen.addEventListener(MouseEvent.MOUSE_OUT, onButtonMouseOut);
  9. btSound.addEventListener(MouseEvent.MOUSE_OVER, onButtonMouseOver);
  10. btSound.addEventListener(MouseEvent.MOUSE_OUT, onButtonMouseOut);

Next, add these two event handler functions. The functions can appear anywhere, but I would suggest placing them after the onInitialize function.

  1. private function onButtonMouseOver(event:MouseEvent):void
  2. {
  3.     event.currentTarget.alpha = 1.0;
  4. }
  5. private function onButtonMouseOut(event:MouseEvent):void
  6. {
  7.     event.currentTarget.alpha = 0.75;
  8. }

When an event occurs, an event object gets passed to the event handler function. The event object has properties that contain information about the event. Here we have used the currentTarget property of the event object to point to the button that triggered the event.

Before we run the application, let’s set the background color in our SitePointTutorial.mxml file.

  1. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:components="components.*" layout="absolute" backgroundColor="#000000">

Now when you hover the mouse over the buttons, you should get a subtle visual clue.

With our slideshow navigation bar looking good, let’s move to the next step — programming our custom component to dispatch an event that contains information on which button was clicked.  Events are how components notify the application (or the larger component that they are part of) that an action has taken place.  Just as each of the five button components dispatched mouseover events that our SlideshowNavigationBar component listened for, the SlideshowNavigationBar component can be programmed to dispatch events that its parent (the application SitePointTutorial) listens for.

“Loosely Coupled” Versus “Tightly Coupled” Components

Before programming the SlideshowNavigationBar to dispatch its own custom events, let’s talk about how you could accomplish the same result without doing so. You could update the SitePointTutorial.mxml file as follows:

  1. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:components="components.*" layout="absolute" backgroundColor="#000000" initialize="onInitialize()">
  2.     <mx:Script>
  3.         <![CDATA[
  4.             private function onInitialize():void
  5.  
  6.             {
  7.                 mySlideshowNavigationBar.btPrev.addEventListener(
  8.                 MouseEvent.CLICK, onPrevClick);
  9.             }
  10.             private function onPrevClick(event:MouseEvent):void
  11.             {
  12.                 trace("Previous Button Clicked");
  13.             }
  14.         ]]>
  15.     </mx:Script>

Here we have registered an event listener on the component btPrev inside our component mySlideshowNavigationBar.  This works, however it is not a best practice.   By doing so, you are creating what is referred to as a “tightly coupled” component.  If you were to use the slideshow navigation bar in several applications and then changed the id of the button to something else (perhaps btPrevSlide), it would break all the applications that relied on the component the next time they were compiled.

A “loosely coupled” component, on the other hand, has a well-defined interface. The internal workings of the component can change, but the interface does not. Information is passed down to the component through the setting of properties. The component passes information back up with event objects (or, in some cases, the parent reads a property of the component at a specific point in time.)

To develop a “loosely coupled” (and thus highly reusable component) you should avoid accessing the component’s own internal components.  So instead of “reaching inside” mySlideshowNavigationBar and registering an event listener on its button, we should register an event listener directly on mySlideshowNavigationBar.  But first we need to program our slideshow navigation bar component to dispatch events.

Creating a Custom Event

The base class for all event objects is the flash.events.Event class.  Subclasses such as the MouseEvent class extend the Event class to contain additional information.  You can see all the standard subclasses listed in the Flex documentation.  You can create your own subclasses of the Event class as well, which is what we will be doing now.

Click File -> New -> ActionScript Class.  Browse and select the events folder as the package. Give it a name of SlideshowNavigationBarEvent. Set the Superclass as flash.events.Event (if you click Browse, it is under Event – flash.events).

Update the code for SlideshowNavigationBarEvent.as to match the following:

  1. package events
  2. {
  3.     import flash.events.Event;
  4.     public class SlideshowNavigationBarEvent extends Event
  5.     {
  6.         public static const PLAY_CLICK:String = "playClick";
  7.         public static const PREVIOUS_CLICK:String = "previousClick";
  8.         public static const NEXT_CLICK:String = "nextClick";
  9.         public static const FULLSCREEN_CLICK:String = "fullscreenClick";
  10.         public static const SOUND_CLICK:String = "soundClick";
  11.  
  12.         public function SlideshowNavigationBarEvent(type:String,
  13.         bubbles:Boolean=false, cancelable:Boolean=false)
  14.         {
  15.             super(type, bubbles, cancelable);
  16.         }
  17.  
  18.         override public function clone():Event
  19.         {
  20.             return new SlideshowNavigationBarEvent(type, bubbles,
  21.             cancelable);
  22.         }
  23.     }
  24. }

In our custom event class we have defined five constants that will be used as the value for the event’s type property. We have defined the constructor to require the type property be set. The constructor also accepts two optional arguments.  All three properties are passed with the super method.  (The super method calls the constructor of the superclass, which is the Event class.)  We also overrode the clone method which is required when creating a subclass of the Event class.

To use our new custom event, add this line to SlideshowNavigationBar.mxml inside the <mx:Script> tag at the top:

  1. import events.SlideshowNavigationBarEvent;

Before the <mx:Script> tag, add the following:

  1. <mx:Metadata>
  2.     [Event(name="playClick", type="events.SlideshowNavigationBarEvent")]
  3.     [Event(name="previousClick", type="events.SlideshowNavigationBarEvent")]
  4.     [Event(name="nextClick", type="events.SlideshowNavigationBarEvent")]
  5.     [Event(name="fullscreenClick", type="events.SlideshowNavigationBarEvent")]
  6.     [Event(name="soundClick", type="events.SlideshowNavigationBarEvent")]
  7. </mx:Metadata>

Now in the onInitialize function in SlideshowNavigatonBar.mxml add the following:

  1. btPrev.addEventListener(MouseEvent.CLICK, onButtonClick);
  2. btNext.addEventListener(MouseEvent.CLICK, onButtonClick);
  3. btPlay.addEventListener(MouseEvent.CLICK, onButtonClick);
  4. btFullScreen.addEventListener(MouseEvent.CLICK, onButtonClick);
  5. btSound.addEventListener(MouseEvent.CLICK, onButtonClick);

Add this event handler function to SlideshowNavigationBar.mxml after the other functions:

  1. private function onButtonClick(event:MouseEvent):void
  2. {
  3.     var eventObj:SlideshowNavigationBarEvent;
  4.     switch (event.currentTarget.id)
  5.     {
  6.         case 'btPrev' :
  7.             eventObj = new SlideshowNavigationBarEvent(
  8.             SlideshowNavigationBarEvent.PREVIOUS_CLICK);
  9.             dispatchEvent(eventObj);
  10.             break;
  11.         case 'btNext' :
  12.             eventObj = new SlideshowNavigationBarEvent(
  13.             SlideshowNavigationBarEvent.NEXT_CLICK);
  14.             dispatchEvent(eventObj);
  15.             break;
  16.         case 'btPlay' :
  17.             eventObj = new SlideshowNavigationBarEvent(
  18.             SlideshowNavigationBarEvent.PLAY_CLICK);
  19.             dispatchEvent(eventObj);
  20.             break;
  21.         case 'btFullScreen' :
  22.             eventObj = new SlideshowNavigationBarEvent(
  23.             SlideshowNavigationBarEvent.FULLSCREEN_CLICK);
  24.             dispatchEvent(eventObj);
  25.             break;
  26.         case 'btSound' :
  27.             eventObj = new SlideshowNavigationBarEvent(
  28.             SlideshowNavigationBarEvent.SOUND_CLICK);
  29.             dispatchEvent(eventObj);
  30.             break;
  31.         default :
  32.             break;
  33.     }
  34. }

Our slideshow navigation bar component will now dispatch events that its parent component can listen for.  Update the SitePointTutorial.mxml as so:

  1. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:components="components.*" layout="absolute" backgroundColor="#000000" initialize="onInitialize()">
  2.     <mx:Script>
  3.         <![CDATA[
  4.             import events.SlideshowNavigationBarEvent;
  5.             private function onInitialize():void
  6.             {
  7.                 mySlideshowNavigationBar.addEventListener(
  8.                 SlideshowNavigationBarEvent.PREVIOUS_CLICK, onPrevClick);
  9.                 mySlideshowNavigationBar.addEventListener(
  10.                 SlideshowNavigationBarEvent.NEXT_CLICK, onNextClick);
  11.                 mySlideshowNavigationBar.addEventListener(
  12.                 SlideshowNavigationBarEvent.PLAY_CLICK, onPlayClick);
  13.                 mySlideshowNavigationBar.addEventListener(
  14.                 SlideshowNavigationBarEvent.FULLSCREEN_CLICK, onFullScreenClick);
  15.                 mySlideshowNavigationBar.addEventListener(
  16.                 SlideshowNavigationBarEvent.SOUND_CLICK, onSoundClick);
  17.             }
  18.             private function onPrevClick(event:SlideshowNavigationBarEvent):void
  19.             {
  20.                 myTextArea.htmlText += "Go to previous slide.<br>";
  21.             }
  22.             private function onNextClick(event:SlideshowNavigationBarEvent):void
  23.             {
  24.                 myTextArea.htmlText += "Go to next slide.<br>";
  25.             }
  26.             private function onPlayClick(event:SlideshowNavigationBarEvent):void
  27.             {
  28.                 myTextArea.htmlText += "Pause slideshow.<br>";
  29.             }
  30.             private function onFullScreenClick(
  31.             event:SlideshowNavigationBarEvent):void
  32.             {
  33.                 myTextArea.htmlText += "Go to full screen.<br>";
  34.             }
  35.             private function onSoundClick(event:SlideshowNavigationBarEvent):void
  36.             {
  37.                 myTextArea.htmlText += "Turn sound off.<br>";
  38.             }
  39.         ]]>
  40.     </mx:Script>
  41.     <mx:HBox width="100%" horizontalAlign="center">
  42.         <mx:TextArea id="myTextArea" width="500" height="400"/>
  43.     </mx:HBox>
  44.     <mx:HBox width="100%" horizontalAlign="center" bottom="60">
  45.         <components:SlideshowNavigationBar id="mySlideshowNavigationBar"/>
  46.     </mx:HBox>
  47. </mx:Application>

When you run the application, take a moment to mentally step through the sequence that occurs when you click the Previous button:

  1. The btPrev component in the mySlideshowNavigationBar component dispatches a click event.
  2. As a result of the click event, the onButtonClick event handler function gets called and creates a new event object that is an instance of SlideshowNavigationBarEvent — a custom class that we created which extends the Event class. This event object has its type property set to the string “previousClick” (defined by the constant PREVIOUS_CLICK). The event is then dispatched with the dispatchEvent method.
  3. In the main application, the function onPrevClick gets called because we registered the appropriate event listener. The application then sends information to the myTextArea component by setting its htmlText property.

This pattern is one that you will use repeatedly in the development of larger Flex applications.  The main application will contain several child components that dispatch events up to the parent application, which then sets properties on other child components.

It is also important to know that custom events can contain much more information than we used in our SlideshowNavigationBarEvent. We only used the type property, which is a standard property of the Event class. We could have extended the Event class to include new variables, including complex objects.

Creating Properties for our Custom Component

There are a few things left to do to make our slideshow navigation bar complete.  A slideshow would probably be playing automatically and when the user clicks the pause button, the application would stop at the current slide and change the pause button to a play button.  To allow for that, let’s add a property to the slideshow navigation bar component called isPlaying.  Add the following code to SlideshowNavigationBar.mxml above the onInitialize function:

  1. private var _isPlaying:Boolean;
  2. public function get isPlaying():Boolean
  3. {
  4.     return _isPlaying;
  5. }
  6. public function set isPlaying(isPlaying:Boolean):void
  7. {
  8.     _isPlaying = isPlaying
  9.     if (_isPlaying == true)
  10.     {
  11.         btPlay.setStyle("upSkin", _pauseIcon);
  12.         btPlay.setStyle("overSkin", _pauseIcon);
  13.         btPlay.setStyle("downSkin", _pauseIcon);
  14.     }
  15.     else
  16.     {
  17.         btPlay.setStyle("upSkin", _playIcon);
  18.         btPlay.setStyle("overSkin", _playIcon);
  19.         btPlay.setStyle("downSkin", _playIcon);
  20.     }
  21. }

We are defining the public property isPlaying by using a setter method.  Using getter and setter methods are not required, but they are a best practice when creating custom components.  Here we have a private variable named _isPlaying.  When this variable changes, the icon for the btPlay button needs to change as well.  Using a setter method allows us to always run code that is associated with the property whenever the property changes.

To simulate a slideshow automatically playing, add the following line to the onInitialize function in SitePointTutorial.mxml:

  1. mySlideshowNavigationBar.isPlaying = true;

Update the onPlayClick function in SitePointTutorial.mxml as so:

  1. private function onPlayClick(event:SlideshowNavigationBarEvent):void
  2. {
  3.     if (mySlideshowNavigationBar.isPlaying == true)
  4.     {
  5.         mySlideshowNavigationBar.isPlaying = false;
  6.         myTextArea.htmlText += "Pause slideshow.<br>";
  7.     }
  8.     else
  9.  
  10.     {
  11.         mySlideshowNavigationBar.isPlaying = true;
  12.         myTextArea.htmlText += "Play slideshow.<br>";
  13.     }
  14. }

Now when you run the application, the dual function of the Play/Pause button will work.

I’ll leave it to you to implement the dual function of the Full Screen and Sound buttons.  You can run the finished application and view the full code.

You can learn more about creating custom components in Adobe Flex by consulting the official documentation.

Notes on learning Cairngorm

Friday, September 12th, 2008

Here is how I added Cairngorm to my skill-set (after first gaining enough Flex experience to understand why you would use an architectural framework).

David Tucker’s 5-Part Series

David’s excellent articles and accompanying videos are what I would highly recommend starting with.

CairngormStore Example

Or rather Douglas McCarroll’s update of Chen Bekor’s ModifiedCairngormStore.  Douglas updated it for Cairngorm 2.2.1 and Flex 3.

10 Tips for Working with Cairngorm

Jesse Warden’s 10 Tips for Working with Cairngorm.  Tip #10 about not having nested views dispatch CairngormEvents (but rather dispatch regular events — or your own custom events that extend the event class — to the main view which would then dispatch a CairngormEvent) is a best practice to keep in mind when studying the CairngormStore example.

Adobe Consulting’s 6-Part Article

Steven Webster and Leon Tanner of Adobe Consulting wrote Developing Flex RIAs with Cairngorm microarchitecture. The article has a lot of really good information in it and uses the CairngormStore as an example (although you will want to get the updated CairngormStore from the link above).

Here are some nuggets I gleaned from the articles:

Nugget #1

…resist the opportunity to scatter state all over your application as strings, numbers, Booleans, and all manner of other primitive objects. …we strongly advocate that the client hold the data — as state, model, or whatever you want to call it — as objects that have semantic meaning.

Source: part 2, page 4

The use of value objects (or data transfer objects) was brought up in other places and is not specific to Cairngorm.  Still, it is good stuff that bears repeating.

Nugget #2

When you create components that rely upon client-side data, it is all too easy to create a direct reference to a Singleton model, such as ShopModelLocator. Indeed this is true throughout the application. We discourage this approach. Instead, consider passing the model and/or its properties down through a hierarchy of your view components, for a cleaner and more thoughtful solution.

Source: part 2, page 7

The goal is to make components more reusable. If you have components importing the modelLocator, using the getInstance method, and then binding directly to data in the Cairngorm modelLocator instance, then those components are coupled to your project and/or the Cairngorm framework.

The argument behind not using CairngormEvents other than in your main view (Jesse Warden’s tip #10 from above) is the same reasoning here — reusablity and loosely coupled components.  Ideally your components could be used outside of the Cairngorm framework.

The CairngormStore example follows this in some places, but not in others. In Main.mxml, for instance, a ProductVO is “injected” into the selectedItem attribute of the <details:ProductDetails> component. However other components, such as ProductsAndCheckoutViewStack.mxml, import the ModelLocator and use ShopModelLocator.getInstance().

Regarding the CairngormStore and best practices, Alistair McLeod states in a comment on his blog that “…you make a good point with CairngormStore – looking at the application now, it definitely does not advertise our best practices of building applications with Cairngorm, and that’s something else we want to rectify…”

I am still trying to fully understand best practices regarding reusability. There is a critique of Cairngorm (or rather a certain use of the Model Locator pattern) here where the author mentions some of this. Another blog post on Cairgorm with some discussion about the Model Locator and coupling components to it is here.

(And just when I thought I was beginning to understand reusability, I saw this session from 360Flex about there being two “levels” of reusability — loosely-coupled components and polished, fit for cataloging/distribution components. The gist being that a truly reusable component takes a lot more time to do and involves such things as allowing css styling and programming default behaviors.)

Nugget #3

Developers should always seek to extract these classes from the Cairngorm architecture. Pulling business logic out of commands and encapsulating into classes is a classic implementation of Extract class refactoring.

Source: part 4, page 5

This is in reference to the AddProductToShoppingCartCommand in CairngormStore. The execute function of this command class just calls a function on shoppingCart stored in the model locator.

  1. ShopModelLocator.getInstance().shoppingCart.addElement(shoppingEvent.product, shoppingEvent.quantity);

Rather than have a lot of complex business logic associated with shopping cart behavior in the AddProductToShoppingCartCommand class, such business logic is encapsulated in a ShoppingCart class (which, in the CairngormStore example, was stored in the model folder.)

Nugget #4

…you and your team will approach the addition of each and every feature to a Cairngorm application in the same way. In short, you will add an event to the controller, register it with a Command class, and implement the Command class to do all the work. With this approach you can drive the development of your application with feature after feature, in a consistent, predictable manner that scales well.

Source: part 4, page 6

This is a good review of the flow in Cairngorm and the benefits of using an architectural framework.

Remote Object for both HTTPS and HTTP

Wednesday, June 11th, 2008

One of my main Flex projects is a content management system.  It is continually being developed and is in production as my employer’s backend.  I hope to make it an even more usable CMS solution by making it where it can be deployed for other websites.

With that future goal in mind, I needed to revisit my services-config.xml file.  I use AMFPHP and had previously hardcoded the endpoint.  We use SSL because the backend is used for more than just editing web pages.  So for it to work on another site, I would needed to edit the services-config.xml file and recompile.

I did a google search on services-config secure and found this page which was very helpful. This page was also an interesting read. Before reading these, I did not know you could use the tokens {server.name} and {server.port} in your services-config.xml file.

Here is my new services-config.xml file that will use SSL if the page is using SSL and regular HTTP if it is not.  I verified it was indeed using SSL with Charles.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <services-config>
  3.  <services>
  4.   <service id="amfphp-flashremoting-service" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage">
  5.    <default-channels>
  6.     <channel ref="my-secure-amfphp"/>
  7.     <channel ref="my-amfphp"/>  
  8.    </default-channels>
  9.    <destination id="amfphp">
  10.     <channels>
  11.      <channel ref="my-secure-amfphp"/>
  12.      <channel ref="my-amfphp"/>
  13.     </channels>
  14.     <properties>
  15.      <source>*</source>
  16.     </properties>
  17.    </destination>
  18.   </service>
  19.  </services>
  20.  <channels>
  21.   <channel-definition id="my-secure-amfphp" class="mx.messaging.channels.SecureAMFChannel">
  22.    <endpoint uri="https://{server.name}:{server.port}/amfphp2/gateway.php" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
  23.    <properties>
  24.     <add-no-cache-headers>false</add-no-cache-headers>
  25.     <polling-enabled>false</polling-enabled>
  26.     <serialization>
  27.      <instantiate-types>false</instantiate-types>
  28.     </serialization>
  29.    </properties>  
  30.   </channel-definition>
  31.   <channel-definition id="my-amfphp" class="mx.messaging.channels.AMFChannel">
  32.    <endpoint uri="http://{server.name}:{server.port}/amfphp2/gateway.php" class="flex.messaging.endpoints.AMFEndpoint"/>
  33.    <properties>
  34.     <add-no-cache-headers>false</add-no-cache-headers>
  35.     <polling-enabled>false</polling-enabled>
  36.     <serialization>
  37.      <instantiate-types>false</instantiate-types>
  38.     </serialization>          
  39.    </properties>  
  40.   </channel-definition>
  41.  </channels>
  42. </services-config>

The only problem with this solution is that when on an http page, the swf will try to access crossdomain.xml via https. On Firefox and IE this silently fails. However on Safari (version 3.1.1 as of this writing) this causes the browser to stall indefinitely if you do not have a signed certificate.

Safari will not prompt you to accept the certificate, nor will it silently fail like the other browsers. Since my application is the backend for a CMS, I just went to the homepage via https and accepted the certificate permanently. However, that would not be a good solution for a public application.