tags:

views:

68

answers:

3

Hi guyz. Today had a look at form layout

<mx:Form id="form">
        <mx:FormItem label="horizontal:">
            <mx:Text text="test"/>
        </mx:FormItem>
</mx:Form>

gives the output in the format 'label' - 'textbox'. But i want to change the orientation without changing the code. Like

label

checkbox

How can i do that. Please explain in detail.

Thanks and Regards,

Karthik Jayaraman

A: 

I think you'll have to override the formItem code and change the way it positions it's children.

I doubt it is trivial, but I wouldn't expect it to be hard. Open up the Form code and review updateDisplayList().

www.Flextras.com
A: 

Your example is not very clear but using the examples below perhaps it will clarify what you are asking

    <mx:Form id="form">
            <mx:FormItem label="Layout Demo:">
                <mx:TextInput text="Input 1"/>
                <mx:TextInput text="Input 2"/>
            </mx:FormItem>
    </mx:Form>

Will render as:

"Layout Demo: | Input 1 |"      
              | Input 2 |

If you want to change it to render horizontally as:

"Layout Demo: | Input 1 |  | Input 2 |" 

add the attribute direction="horizontal" to the <mx:FormItem>

Saheed
A: 

I've done this, with the label vertically above the form item in Flex 3, and it was not pretty. It worked, but it wasn't the most stable and required a serious amount of hacking of the Component Life Cycle methods.

In Flex 4, the new skinning model fixes this, use that if possible, coming in the Hero release (http://blog.flexexamples.com/2010/08/28/creating-a-simple-spark-form-in-flex-hero/).

Here are the relevant bits of the code I used to override in Flex 3 and is one approach to it, YMMV.

NB This component had a lot more in it, I took stuff out so is given as a rough guide to how I approached it, it may not run as is, but with some tweaking it should be okay.

package my.controls
{
import flash.display.DisplayObject;

import mx.containers.FormItem;
import mx.containers.FormItemDirection;
import mx.core.EdgeMetrics;
import mx.core.IDeferredInstance;
import mx.core.UIComponent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;

/**
 * Allows positioning of the FormItem labels to be as inherited from FormItem, or
 * allows for top alignment of labels whilst resizing form elements to fill width
 * of parent container.
 *
 * <p>Possible values are: <code>left</code>; and <code>top</code>.</p>
 * <p>Default value is: left.</p>
 */
[Style(name="labelAlign", type="String", enumeration="left,top", inherit="no")]

public class MyFormItem extends FormItem
{
    private static const labelAlignStyleName:String = "labelAlign";

    private static var classConstructed:Boolean = constructCustomStyle();

    private var _isFormItemLabelTopAligned:Boolean;

    private var _hasFormItemLabel:Boolean;

    /**
     * This calls the super.commitProperties() method and then sets instance variables
     * indicating on whether the labels should be top aligned, and also if there is
     * a non zero length string as the label value.
     */
    override protected function commitProperties():void
    {
        super.commitProperties();

        if (label != null && label.length > 0)
        {
            _hasFormItemLabel = true;
        }

        if (getStyle(labelAlignStyleName) == "top")
        {
            _isFormItemLabelTopAligned = true;
        }
    }

    /**
     * After calling the super.measure() method, a check is made to see if there is a label
     * value, and if the label is top aligned.  If so, it modifies the measuredHeight and
     * measuredMinHeight to include the height of the label, plus the indicatorGap style value
     */
    override protected function measure():void
    {
        super.measure();

        //If this is top aligned, then change the height of this component
        if (_isFormItemLabelTopAligned)
        {
            if (_hasFormItemLabel)
            {
                var addedHeight:Number = itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName);

                //Add the height of the label
                measuredHeight = measuredHeight + addedHeight;
                measuredMinHeight = measuredMinHeight + addedHeight;
            }

            //Ignore any measured width we may get, caclulate the width as being the greater of the label or child
            var children:Array = getChildren();
            var maxChildWidth:Number = 0;

            //Usually there is only 1 child, but code for in case there are more
            for each (var child:DisplayObject in children)
            {
                var uiComp:UIComponent = child as UIComponent;
                maxChildWidth = Math.max(maxChildWidth, uiComp.getExplicitOrMeasuredWidth());
            }

            //Set the measurement
            measuredWidth = measuredMinWidth = Math.max(itemLabel.getExplicitOrMeasuredWidth(),
                                                        maxChildWidth);
        }
    }

    /**
     * <p>Checks for label top alignment and if so, changes the location of label to be
     * moved to the top left of the container.  It then proceeds to layout the children depending on
     * whether the <code>MyFormItem.direction</code> property is horizontal or vertical.</p>
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);

        if (_isFormItemLabelTopAligned)
        {
            //Anchor the label to the top left of the container
            if (_hasFormItemLabel)
            {
                var vm:EdgeMetrics = viewMetricsAndPadding;
                itemLabel.move(vm.left, vm.top);
            }

            //Depending on the layout of the MyFormItem items, take appropriate action
            if (numChildren > 0)
            {
                if (direction == FormItemDirection.VERTICAL)
                {
                    moveVerticalLayoutWithTopLabel();
                }
                else
                {
                    moveHorizontalLayoutWithTopLabel();
                }
            }

            findAndRemoveIndicator();
        }

    }

    /**
     * <p>Given that labelAlign is set to be top aligned, this method works out the  new
     * positioning and size of the vertically aligned children.  It uses the <code>verticalGap</code>
     * style property to set a width between the children.</p>
     */
    protected function moveVerticalLayoutWithTopLabel():void
    {
        var vm:EdgeMetrics = viewMetricsAndPadding;
        var commonX:Number = vm.left;

        var child:UIComponent;
        var previousChild:UIComponent = null;
        var newY:Number = vm.top + (_hasFormItemLabel ? (itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName)) : 0);
        var availableWidth:Number = this.width - getStyle("paddingLeft") - getStyle("paddingRight");

        for (var i:Number = 0; i < numChildren; i++)
        {
            //Calculate the new Y value of the child including the vertical gap
            if (previousChild != null)
            {
                newY = newY + previousChild.getExplicitOrMeasuredHeight() + getStyle("verticalGap");
            }

            child = UIComponent(getChildAt(i));
            child.move(commonX, newY);

            //If these children are percentWidths, then set the size of them as we have more width now
            if (!isNaN(child.percentWidth))
            {
                var childSize:Number = availableWidth * (child.percentWidth / 100);
                child.setActualSize(childSize, child.getExplicitOrMeasuredHeight());
            }

            previousChild = child;
        }
    }


    /**
     * <p>Given that labelAlign is set to be top aligned, this method works out the  new
     * positioning and size of the horizontal aligned children.  It uses the <code>horizontalGap</code>
     * style property to set a width between the children.</p>
     */
    protected function moveHorizontalLayoutWithTopLabel():void
    {
        var child:UIComponent;
        var previousChild:UIComponent = null;
        var vm:EdgeMetrics = viewMetricsAndPadding;
        var commonY:Number = vm.top + (_hasFormItemLabel ? (itemLabel.getExplicitOrMeasuredHeight() + getStyle(indicatorGapStyleName)) : 0);
        var newX:Number = vm.left;
        var availableWidth:Number = this.width - getStyle("paddingLeft") - getStyle("paddingRight");

        for (var i:int = 0; i < numChildren; i++)
        {
            //Calculate the new X postion of the child taking into account of the label move
            if (previousChild != null)
            {
                newX = newX + previousChild.width + getStyle("horizontalGap");
            }

            child = UIComponent(getChildAt(i));
            child.move(newX, commonY);

            //If these children are percentWidths, then set the size of them as we have more width now
            if (!isNaN(child.percentWidth))
            {
                var childSize:Number = availableWidth * (child.percentWidth / 100);
                child.setActualSize(childSize, child.getExplicitOrMeasuredHeight());
            }

            previousChild = child;
        }
    }

    private function findAndRemoveIndicator():void
    {
        if (!required)
        {
            return;
        }

        var indicatorClass:Class = getStyle("indicatorSkin") as Class;
        var rawChildrenLength:int = rawChildren.numChildren;

        for (var i:int = 0; i < rawChildrenLength; i++)
        {
            var displayObject:DisplayObject = rawChildren.getChildAt(i);
            if (displayObject is indicatorClass)
            {
                rawChildren.removeChild(displayObject);
                return;
            }
        }
    }

    /**
     * A convenience method to register a style with the StyleManager and create the necessary
     * defaults.
     */
    private static function constructCustomStyle():Boolean
    {
        const className:String = "MyFormItem";
        const defaultLabelAlignment:String = "left";
        const defaultIndicatorGap:Number = 2;

        if (!classConstructed)
        {
            var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration(className);

            if (style)
            {
                if (style.getStyle(labelAlignStyleName) == undefined)
                {
                    style.setStyle(labelAlignStyleName, defaultLabelAlignment);
                }
            }
            else
            {
                style = new CSSStyleDeclaration();
                style.defaultFactory = function():void
                {
                    this[labelAlignStyleName] = defaultLabelAlignment;
                }

                StyleManager.setStyleDeclaration(className, style, true);
            }
        }
        return true;
    }

    /**
     * Calls super.styleChanged() method.
     *
     * If the labelAlign style changes, then the component is invalidated as
     * its properties and size recalculated as well as being redrawn.
     */
    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged(styleProp);

        // Check to see if style changed. 
        if (styleProp == labelAlignStyleName)
        {
            invalidateProperties();
            invalidateSize();
            invalidateDisplayList();
        }
    }
}

}

David Collie