Friday, June 17, 2011

Flex: dragDropEvent is not dispatched if feedback is set to none

A few days ago I posted about how setting the useRollOver style to false on a List control also prevents the itemRollOver event from firing on that same control:

Flex: Changing useRollOver style affects itemRollOver event

In other words, what I thought would only affect the visual appearance of a component turned out to also have an impact on the components logic. I realized that some time ago I ran in to that same scenario in the context of drag & drop. I thought I'd share that here as well.

So, basically, I was implementing some drag and drop functionality for our application. When the user started a drag action I changed some parameters in the application to reflect that a dragging was currently under way. When the user stopped his dragging I was relying on the dragDropEvent to restore the application to its original state or update it depending on the drop location.

The above was implemented and was working fine. Next step was to show different cursor icons depending on which areas of the screen the user was currently dragging over, the green plus for allowed drop locations and the red cross for prohibited locations. This was easily accomplished by using the DragManager.showFeedback function passing it DragManager.COPY or DragManager.NONE respectively. Unfortunately it showed that this change also caused the implemented logic to fail. The dragDropEvent handler was just not invoked if the feedback was set to none.

Looking back at it now, I suppose it kind of makes sense and my implementation became a bit more clear after reorganizing it to account for the encountered issue. Still, it took me a while to figure it out and I also couldn't find any mentioning of this 'feature' in the documentation. It just doesn't feel right when changing the visual feedback of your application also affects the functionality. MVC, anyone?

Tuesday, June 14, 2011

Flex: Changing useRollOver style affects itemRollOver event

The other day I ran into a bug in our application that was a bit unintuitive to track down. Suddenly the context menus used on a Tree component in our application started to behave weird. Anytime the context menu was opened the menu items in it referred to the first item in the Tree rather than to the item below the mouse cursor. To give some context, this is how the context menu logic for the Tree component is setup:

We have a variable called 'lastRollOverIndex' that is updated whenever the user hovers an item in the Tree. This variable is updated with the Tree index of the last rolled over item using the itemRollOver event of the Tree control. Whenever the user then right clicks the Tree control the lastRollOverIndex is used to get the item currently under the mouse cursor and the statuses and captions of the context menu items are updated accordingly before the context menu is shown to the user.

And now, seemingly out of the blue, the above functionality failed to work. So I started going through the recent change sets in our repository to try to find out what could be the cause of this problem. Interestingly, the search showed that no changes had been made to any files containing the logic for the above, the only change between a working and a non-working version of the application was some updates to the stylesheet.

But, could that really be? Updating some styles to changes the visual appearance of the application would also cause the logic of the same to fail? Well, answer is yes.

So one of the changes in the stylesheets was setting useRollOver to false for all List components to turn of the highlighting of items while hovering the list. Since a Tree is an extension of a List the change also applies to Tree components. So far, so good. But now we come to the unexpected part. Turns out, setting the useRollOver style also prevents the itemRollOver event from being dispatched which in turn killed the logic for our context menus. At least to me, this was not what I expected, and from what I could find in the documentation it is also not mentioned there.

So in order to disable the highlighting but still allow the itemRollOver events to fire I chose to override the drawItem function of the Tree control:

protected override function drawItem(item:IListItemRenderer,
    selected:Boolean = false,
    highlighted:Boolean = false,
    caret:Boolean = false,
    transition:Boolean = false):void
{
    super.drawItem(item, selected, false, caret, transition);
}

Friday, April 29, 2011

Flex: Custom context menu

Background

I never really liked the context menus supplied by the flash player that you are bound to use when developing flex applications. Mostly because of the non-customizable look and feel and the fact that your application specific menu items will be in the same list as the built in items (you can't hide all of them). Since we are using context menus in many places in our applications I have been looking at the following solution for a while to replace the built in menus:

http://code.google.com/p/custom-context-menu/

This solution uses javascript to capture the right click event over the SWF to prevent the flash player from opening its built-in context menu. The event is then passed on to a function that communicates with Flash over the ExternalInterface. The function called in action script is then free to do whatever it likes to act upon the performed right click.

The drawback of the solution is that it relies on using wmode='opaque' for the flash object and this does not work reliably across different browsers and operating systems.

Anyway, when running into the following bug with the latest Safari browser (5.0) on Mac:

http://bugs.adobe.com/jira/browse/FP-4825

I decided to go ahead and implement custom context menus, at least for our Mac users.

Implementation

The implementation relies on the solution mentioned above for handling the right click events and passing them on to the Flex application. An action script function then traverses the components currently located below the mouse pointer until it finds one that has a context menu defined. The found ContextMenu object then gets wrapped in a CustomContextMenu wrapper that uses the original context menu to create and show a regular flex menu.

The CustomContextMenu wrapper also makes sure that the enabled and visible statuses of the context menu items are also reflected in the items of the generated menu. Last but not least the wrapper makes sure that any user actions on the generated menu are dispatched as events on the original context menu so that the behavior of the original context menu is preserved in the new menu.

That's it. Enough talking. Let's look at the code, feel free to re-use it in any way you deem fit:

This is the code for the CustomContextMenu wrapper:

CustomContextMenu.as

package com.flexceptional.components
{
    import flash.events.ContextMenuEvent;
    import flash.ui.ContextMenu;
    import flash.ui.ContextMenuItem;
    
    import mx.controls.Menu;
    import mx.events.MenuEvent;
    
    public class CustomContextMenu
    {
        private static var SEPARATOR:Object = {type: "separator"};
        
        private var _menu:Menu;
        private var _contextMenu:ContextMenu;
        
        public function CustomContextMenu(contextMenu:ContextMenu)
        {   
            _contextMenu = contextMenu;
            
            // Create menu
            _menu = Menu.createMenu(null, null);
            
            // Add event listeners
            _menu.addEventListener(MenuEvent.MENU_SHOW, menuShowHandler);
            _menu.addEventListener(MenuEvent.ITEM_CLICK, itemClickHandler);
            
            // Set properties
            _menu.setStyle("openDuration", 0);
            _menu.variableRowHeight = true;
            _menu.id = "contextMenu";
            
            // Create custom menu items
            var menuItems:Array = [];
            for each (var item:ContextMenuItem in contextMenu.customItems)
            {
                if (item.separatorBefore)
                {
                    menuItems.push(CustomContextMenu.SEPARATOR);
                }
                menuItems.push(new CustomContextMenuItem(item));
            }
            _menu.dataProvider = menuItems;
        }
        
        private function menuShowHandler(event:MenuEvent):void
        {
            _contextMenu.dispatchEvent(new ContextMenuEvent(ContextMenuEvent.MENU_SELECT));
        }
        
        private function itemClickHandler(event:MenuEvent):void
        {
            event.item.dispatchEvent(new ContextMenuEvent(ContextMenuEvent.MENU_ITEM_SELECT));
        }
        
        public function show(xShow:Object = null, yShow:Object = null):void
        {
            _menu.show(xShow, yShow);
        }
        
        public function hide():void
        {
            _menu.hide();
        }
    }
}

import flash.events.ContextMenuEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.ui.ContextMenuItem;

// ContextMenuItem wrapper
class CustomContextMenuItem extends EventDispatcher
{   
    private var _contextMenuItem:ContextMenuItem;
    
    public function CustomContextMenuItem(contextMenuItem:ContextMenuItem)
    {
        _contextMenuItem = contextMenuItem;
        
        // Dispatch event on the proxied ContextMenuItem
        addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, function(event:Event):void
        {
            _contextMenuItem.dispatchEvent(event);
        });
    }
    
    public function get label():String
    {
        return _contextMenuItem.caption;
    }
    
    public function get enabled():Boolean
    {
        return _contextMenuItem.enabled;
    }
    
    public function get visible():Boolean
    {
        return _contextMenuItem.visible;
    }
}

This is the function invoked by the java script, place it in your main application (the optional x and y arguments can be used for automated testing):

private var customContextMenu:CustomContextMenu;

private function rightClickHandler(clickedX:Number = NaN, clickedY:Number = NaN):void {
    // Close previous context menu
    if (customContextMenu)
    {
        customContextMenu.hide();
    }
    
    clickedX = isNaN(clickedX) ? mouseX : clickedX;
    clickedY = isNaN(clickedY) ? mouseY : clickedY;
    
    // Get objects under clicked point
    var objects:Array = systemManager.getObjectsUnderPoint(new Point(clickedX, clickedY));
    if (objects.length > 0)
    {
        // Get the top most object clicked
        var object:Object = objects[objects.length-1];
        
        // Check if any of the parent objects have
        // a context menu defined
        while (object)
        {
            if (object.hasOwnProperty("contextMenu") && object.contextMenu)
            {
                customContextMenu = new CustomContextMenu(object.contextMenu);
                customContextMenu.show(clickedX, clickedY);
                break;
            }
            object = object.parent;
        }
    }
}

The code snippet for making the above function available through the ExternalInterface, place it in the initializeHandler of your main application file:

// Enable custom right click functionality
ExternalInterface.addCallback("rightClick", rightClickHandler);

We use the following javascript function to initialize the right click handling only for Mac users running Safari 5, place it in your html template and call it on the onload event on the body tag:

function initRightClick() {
    if (BrowserDetect.OS == "Mac" &&
        BrowserDetect.browser == "Safari" &&
        BrowserDetect.version == "5") {
        RightClick.init();
    }
}

The BrowserDetect object referenced in the above function comes from a file created using the code posted on this page:

http://www.quirksmode.org/js/detect.html

So, big post, feel free to comment on it if you have any questions or doubts. The solution provides a nice workaround for the Mac Safari 5 context menu bug and since our context menus can now be invoked by calling a function from javascript it also allows for us to do automated testing of our context menus using our integration test setup. More on testing in a later post.

Friday, April 15, 2011

Install stand alone Flash player on 64 bit Ubuntu

We are running our Flex unit tests on a 64 bit Ubuntu machine as part of our continuos integration environment. In order to run the tests we need to have the stand alone version of the Flash player installed. Unfortunately there is not yet a 64 bit version of the flash player projector for linux so to run our tests we need to use the 32 bit version.

These are the steps needed to install the 32 bit stand alone flash player under a 64 bit Ubuntu:

  1. Download the 32 bit Flash player projector from http://www.adobe.com/support/flashplayer/downloads.html

    > wget http://download.macromedia.com/pub/flashplayer/updaters/10/flashplayer_10_sa.tar.gz

  2. Unpack the flash player executable and make sure you put it on your path.

    > tar -xzvf flashplayer_10_sa.tar.gz

    Now, if you try to run the Flash player, chances are you will get an error message similar to the following:

    > flashplayer
    flashplayer: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory

    This is because the 32 bit Flash player also requires 32 bit versions of certain libraries to be installed. If you run ldd (print shared library dependencies) on the flash player executable you will be presented with a list of the required libraries:

    > ldd flashplayer

  3. Install required 32 bit libraries. Luckily, most of these libraries can be installed by installing a single package using apt-get:

    > apt-get install ia32-libs

    If you now run ldd on the flash player again you should see that the library dependencies have been resolved and, if you are lucky, the flash player will now launch.

Good luck!

Note that these were the steps required for our installation, another platform might require additional steps or libraries.

Wednesday, March 30, 2011

Adding a button (or whatever) to a Panel header

The other day I had the need to add an Image control to the header area of a Panel. Since the Panel component does not support adding things to its header I had to find my own solution. My first simple approach was to just add the Image to the Panel content and then tweak the paddingTop style of the Panel to make the Image appear in the header. This worked for appearance but any mouse interaction on the Image was blocked. So I looked around a bit and found several solutions for extending the Panel component to add a single button to the Panel header. Since, in my case, I needed to add an Image and I figured in other use cases I might want to add several Buttons or a ButtonBar or whatever I decided to extend the Panel class to create a generic Panel component that would accept and place any kind of UIComponent in its header.
Here is the code. Feel free to re-use it in any way you deem fit.

package com.flexceptional.components
{
   import mx.containers.Panel;
   import mx.core.UIComponent;

   public class ExtendedPanel extends Panel
   {
       public var headerPanel:UIComponent;

       override protected function createChildren():void
       {
           super.createChildren();

           // Add the title panel
           if (headerPanel)
           {
               titleBar.addChild(headerPanel);
           }
       }

       override protected function updateDisplayList
(unscaledWidth:Number, unscaledHeight:Number):void
       {
           super.updateDisplayList(unscaledWidth, unscaledHeight);

           // Position the title panel
           if (headerPanel)
           {
               var y:int = (titleBar.height - headerPanel.height) / 2;
               var x:int = this.width - headerPanel.width - 5;
               headerPanel.move(x, y);
           }
       }
   }
}

Now, thanks to the convenience of MXML and the way it is compiled, we can specify our additions to the Panel header in MXML when defining the Panel.
Example:

<comp:ExtendedPanel width="100%" height="100%" title="Properties">
   <comp:headerPanel>
       <mx:Image source="{helpIcon}"
                 width="16"
                 height="16"
                 toolTip="Open help documentation in new window"
                 useHandCursor="true"
                 buttonMode="true"
                 mouseChildren="false"
                 click="propertyHelpIcon_clickHandler(event)" />
   </comp:headerPanel>
   <!-- Panel content -->
   <comp:PropertiesGrid />
</comp:ExtendedPanel>

Tuesday, March 29, 2011

Flex: Disable mouse wheel scrolling for Text

In several places in our application we have Text components that dynamically resize depending on their content. An annoying thing about these Text components is that they start scrolling when the user uses the mouse wheel while hovering the components. I have been trying to disable this scrolling for a while but all of the proposed solutions* that I have found have not worked.

It was only today when I broadened my search criteria to target the Flash community rather than limit the search to Flex that I finally found a solution:

http://www.eddieoneverything.com/articles/disabling-textarea-scrolling-in-flash-as3.php

Based on the solution proposed in this link I created a new text component for Flex that does not allow the user to scroll the content.

Here is the code. Feel free to re-use it in any way you deem fit.

package com.flexceptional.components
{
    import flash.events.Event;
    import flash.text.TextFieldAutoSize;
    
    import mx.controls.Text;
    import mx.events.FlexEvent;
    
    public class TextNoScroll extends Text
    {
        override protected function createChildren():void
        {
            super.createChildren();
            
            // To get rid of unwanted scrolling. Needs to
            // be done any time the component is resized
            // or the content changes.
            addEventListener(FlexEvent.UPDATE_COMPLETE,
                             updateCompleteHandler);
        }
        
        private function updateCompleteHandler(event:Event):void
        {
            textField.autoSize = TextFieldAutoSize.LEFT;
            var tempHeight:Number = textField.height;
            textField.autoSize = TextFieldAutoSize.NONE;
            textField.height = tempHeight + 20; // Padding 20px
        }
    }
}

* Proposed solutions that did not work:

  • Setting mouseWheelEnabled=false on the internal textfield
  • Stopping propagation of any mouse wheel events on the text component or the internal text field
  • Setting mouseEnabled = false on the Text component. Well, this actually does disable the scrolling but it also disables any other mouse action, like selecting text or clicking links.

Wednesday, March 16, 2011

FITC Amsterdam 2011

Last week i went to the FITC conference in Amsterdam. Apart from the great parties these are some of the highlights from my visit there.

About the conference

FITC claims to be a conference for “Digital Creators” and its tagline for the Amsterdam edition is “Design. Technology. Cool shit.”. The audience was a blend of designers and developers, students, freelancers and design studios. The presentations covered areas from technical subjects on Flash, Flex, 3D and mobile development to talks about design and even business and marketing. There was also a keynote presentation from Adobe that covers the new developments in their frameworks and tools.

Many important people from Adobe were also there giving presentations and talking to the members of the community. Including people from the FlashBuilder and Flex SDK program management team and people from the Flash Player engineering team.

I found that most people I talked to were more targeted towards Flash design and development, making web page designs and advertising campaigns for their clients. Judging by the well worked out mixture of presenters and the diversity of the given presentations I am sure that there were also a lot of Flex developers present but for some reason they were a bit harder to find, at least for me.

Flash vs HTML5

There were a lot of talks about HTML5 on the conference and as to whether or not the new capabilities of HTML5 will put Flash out of a job. The general conclusion was that this might eventually be the case but not in a near foreseeable future. Even though the new HTML5 canvas and CSS3 specifications will allow for a lot more elaborate user experiences in a browser this is still a very immature technology. A few of the things that should make you think twice before switching over to HTML5 for your application development are:
  • Browser support is still bad. Many major browsers are still not close to implementing all the new features of the HTML5 and CSS3 specifications meaning that the cool new stuff you implement might not even work in all browsers.
  • Cross browser compatibility. Different browsers implement the new capabilities in different ways leading to you having to code around the differences to maintain a consistent user experience across different browsers.
  • Performance. The different implementations of HTML5 canvas still require a lot of processing power so switching to HTML5 to support more mobile platforms is not a good idea yet.
  • Tool support. In terms of development experience and productivity people have already started to develop nice new tools and APIs towards the new HTML5 and CSS3 specifications but there is still a long way to go.

New Flex stuff

The upcoming releases of the Flash Builder and Flex SDK focus on mobile application development and include a lot of features and tools for targeting a mobile market. One of the improvements that will also benefit the development of applications for desktop pcs are the set of optimized components that come with the new releases. If you are still developing using the Flex 3 SDK and would like to start doing something for mobile it is time to make the switch to Flex 4 and Spark. MX components will have limited support on mobile devices.

Concurrency in the Flash Player

Jim Corbett of the Adobe Flash Player team did a nice presentation about the news in the Flash Player. The main new features were the support for native GPU acceleration for rendering videos and 3D graphics and the addition of a low level 3D API for 3D development named “Molehill”. Even if Jim stressed the fact that it is not really official and that there is no release date set for it the Flash Player team is finally starting to think about adding concurrency, support for threads, in the Flash Player programming and execution model.

Conclusion

This year was my first time at FITC Amsterdam and I had a great time. I think that the conference was very valuable and rewarding, both for me personally and for my company in terms of contacts with people from the industry and community and new insights in the recent developments of tools and technologies for building user experiences. I hope to be able to come back next year!