Tuesday, November 23, 2010

Seriously, how to position your UIComponent in Flex?

Believe it or not, but recently I struggled to achieve something so simple like positioning a Button by X and Y. What was the case:

We have a Button with X and Y values binded. We also set depth to this button, lets say 1. And we want at some point to change its position. The code for the whole application looks like

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
         xmlns:s="library://ns.adobe.com/flex/spark" 
         xmlns:mx="library://ns.adobe.com/flex/mx">
  
  <fx:Script>
    <![CDATA[
      [Bindable]
      public var buttonX:Number;
      [Bindable]
      public var buttonY:Number;
    ]]>
  </fx:Script>
  
  <s:Button label="Target" x="{buttonX}" y="{buttonY}" depth="1"/>
  <s:HGroup horizontalCenter="0" verticalCenter="0" >
    <s:Button label="buttonX = 100" click="buttonX = 100"/>
    <s:Button label="buttonY = 100" click="buttonY = 100"/>
  </s:HGroup>
  
</s:Application>

It looks pretty straight-forward. But there is a catch. Here is the application. Try to move the button, I dare you :)



There are 2 important things that are happening here:

1. By default buttonX and buttonY are NaN. That basically is not a problem, because we will set actual values to them and they will change, but if it wasn't for
2. When we set depth to an instance of UIComponent, the special layoutFeatures are being initialized. They are responsible for advanced features of the layout such as:
depth, z, rotationX, rotationY, transformX, transformY and so on..

So once these layoutFeatures get initialized, for every new assign of x, y, width, height scale.. it uses the layoutFeatures object of UIComponent to set them. And this time it is not overwriting the values, instead it is doing a translation (when setting x). The actual thing it is being done is : translateBy(value-_x,0,0); where _x is the old x, and value is the new that we are assigning. So when our old value is NaN...
you guessed NaN - whatever_number = NaN

So I wondered whether this is a bug and should be reported to Adobe (if it already isn't). But I am this type of a guy where I always blame myself, so I think maybe the guilt is mine. So I have to fix my code. The easiest and proper fix is to assign default values of 0 to the properties we are binding ... or not using depth at all, who needs it ;)

[Bindable]
public var buttonX:Number = 0;
[Bindable]
public var buttonY:Number = 0;