////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2010 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.components
{
import flash.events.Event;

import mx.core.IVisualElement;
import mx.core.UIComponent;
import mx.core.mx_internal;

import spark.core.IDisplayText;

use namespace mx_internal;

/**
 *  Specifies the image source to use for the required indicator. 
 *
 *  The default value is "assets/RequiredIndicator.png".
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
[Style(name="requiredIndicatorSource", type="Object", inherit="no")]

/**
 *  Specifies the image source to use for the error indicator. 
 *
 *  The default value is "assets/ErrorIndicator.png".
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5
 */
[Style(name="errorIndicatorSource", type="Object", inherit="no")]

/**
 *  The FormItem container defines the following in a Spark From:
 *
 *  <ul>
 *    <li>A single label.</li>
 *    <li>A sequence label.</li>
 *    <li>One or more child controls or containers.</li>
 *    <li>Help content that provides a description of the form item 
 *      or instructions for filling it in.</li>
 *    <li>Required indicator to indicate if a form item has to be filled</li>
 *  </ul>
 *
 *  Children can be controls or other containers.
 *  A single Form container can hold multiple FormItem containers.
 *  By default, all the FormItem elements are arranged in a horizontal 
 *  layout with the label placed on the left and the Help content on the right.
 *
 *  @mxml
 *
 *  <p>The <code>&lt;s:FormItem&gt;</code> tag inherits all the tag 
 *  attributes of its superclass and adds no new tag attributes:</p>
 *
 *  <pre>
 *  &lt;s:FormItem
 *    <strong>Properties</strong>
 *    helpContent="null"
 *    label=""
 *    required="false"
 *    sequenceLabel=""
 *  
 *    <strong>Common Styles</strong>
 *    errorIndicatorSource="assets/ErrorIndicator.png"
 *    requiredIndicatorSource="assets/RequiredIndicator.png"
 * 
 *    <strong>Mobile Styles</strong>
 *    leading="2"
 *    letterSpacing="0"
 *  /&gt;
 *  </pre>
 * 
 *  @see spark.components.Form
 *  @see spark.components.FormHeading
 *  @see spark.layouts.FormLayout
 *  @see spark.skins.spark.FormItemSkin
 *
 *  @includeExample examples/FormItemExample.mxml
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.5
 *  @productversion Flex 4.5 
 */
public class FormItem extends SkinnableContainer
{
    include "../core/Version.as";
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function FormItem()
    {
        super();
        // Set these here instead of in the CSS type selector for Form
        // We want to hide the fact that the Form itself doesn't show
        // the error skin or error tip, but that its children do. 
        setStyle("showErrorSkin", false);
        setStyle("showErrorTip", false);
        
        showInAutomationHierarchy = false;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Skin Parts
    //
    //--------------------------------------------------------------------------
    
    /**
     *   A reference to the visual element that displays this FormItem's label.
     */
    [SkinPart(required="false")]
    public var labelDisplay:IDisplayText;
            
    /**
     *  A reference to the visual element that displays the FormItem's sequenceLabel.
     */
    [SkinPart(required="false")]
    public var sequenceLabelDisplay:IDisplayText;
    
    /**
     *  A reference to the Group that contains the FormItem's helpContentGroup.
     */
    [SkinPart(required="false")]
    public var helpContentGroup:Group;
    
    /**
     *  A reference to the visual element that display the FormItem's error strings.
     */
    [SkinPart(required="false")]
    public var errorTextDisplay:IDisplayText;
    
    //--------------------------------------------------------------------------
    //
    //  Properties 
    //
    //--------------------------------------------------------------------------
    
    mx_internal var _elementErrorStrings:Vector.<String> = new Vector.<String>;
    
    /**
     *  Each Vector item contains the error string from a content element. 
     *  If none of the content elements are invalid, then the vector 
     *  is empty. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */     
    [Bindable(event="elementErrorStringsChanged")]
    
    public function get elementErrorStrings():Vector.<String>
    {
        return _elementErrorStrings;
    }
     
    //----------------------------------
    //  helpContent
    //----------------------------------

    private var _helpContent:Array;
    private var helpContentChanged:Boolean = false;
    
    [ArrayElementType("mx.core.IVisualElement")]
    [Bindable("helpContentChanged")]
    [Inspectable(category="General", arrayType="mx.core.IVisualElement", defaultValue="")]
    
    /** 
     *  The set of components to include in the help content 
     *  area of the FormItem.
     * 
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get helpContent():Array
    {
        if (helpContentGroup)
            return helpContentGroup.getMXMLContent();
        else
            return _helpContent;
    }
    
    /**
     *  @private
     */
    public function set helpContent(value:Array):void
    {
        _helpContent = value;
        helpContentChanged = true;
        invalidateProperties();
    }
    
    //----------------------------------
    //  label
    //----------------------------------
    
    private var _label:String = "";
    
    [Bindable("labelChanged")]
    [Inspectable(category="General", defaultValue="")]
    
    /**
     *  Text label for the FormItem.
     *  For example, a FormItem used to input an 
     *  address might have the label of "Address".
     * 
     *  @default ""
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get label():String
    {
        return _label;
    }
    
    /**
     *  @private
     */
    public function set label(value:String):void
    {
        if (_label == value)
            return;
        
        _label = value;
        
        if (labelDisplay)
            labelDisplay.text = label;
        dispatchEvent(new Event("labelChanged"));
    }
            
    //----------------------------------
    //  required
    //----------------------------------
    
    private var _required:Boolean = false;
    
    [Bindable("requiredChanged")]
    [Inspectable(category="General", defaultValue="false")]
    
    /**
     *  If <code>true</code>, puts the FormItem skin into the
     *  <code>required</code> state. By default, this state displays 
     *  an indicator that the FormItem children require user input.
     *  If <code>false</code>, the indicator is not displayed.
     *
     *  <p>This property controls skin's state only.
     *  You must assign a validator to the child 
     *  if you require input validation.</p>
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get required():Boolean
    {
        return _required;
    }
    
    /**
     *  @private
     */
    public function set required(value:Boolean):void
    {
        if (value == _required)
            return;
        
        _required = value;
        invalidateSkinState();
    }
    
    //----------------------------------
    //  sequenceLabel
    //----------------------------------
    
    private var _sequenceLabel:String = "";
    
    [Bindable("sequenceLabelChanged")]
    [Inspectable(category="General", defaultValue="")]
    
    /**
     *  The number of the form item in the form. 
     * 
     *  @default ""
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    public function get sequenceLabel():String
    {
        return _sequenceLabel;
    }
    
    /**
     *  @private
     */
    public function set sequenceLabel(value:String):void
    {
        if (_sequenceLabel == value)
            return;
        
        _sequenceLabel = value;
        
        if (sequenceLabelDisplay)
            sequenceLabelDisplay.text = sequenceLabel;
        dispatchEvent(new Event("sequenceLabelChanged"));
        
        invalidateProperties();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Overridden Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  baselinePosition
    //----------------------------------
    
    /**
     *  @private
     */
    override public function get baselinePosition():Number
    {
        return getBaselinePositionForPart(labelDisplay as IVisualElement);
    }     
    
    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------
 
    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
        
        if (helpContentChanged)
        {
            createHelpContent();
            helpContentChanged = false;
        }
    }
    
    /**
     *  @private
     */
    override protected function getCurrentSkinState():String
    {
        if (required)
        {
            if (!enabled)
                return "requiredAndDisabled";
            else if (elementErrorStrings.length > 0)
                return "requiredAndError";
            else
                return "required";
        }
        else
        {
            if (!enabled)
                return "disabled";
            else if (elementErrorStrings.length > 0)
                return "error";
            else
                return "normal";       
        }
    }
    
    /**
     *  @private
     */
    override protected function partAdded(partName:String, instance:Object) : void
    {
        super.partAdded(partName, instance);
        
        if (instance == labelDisplay)
            labelDisplay.text = label;
        else if (instance == sequenceLabelDisplay)
            sequenceLabelDisplay.text = sequenceLabel;
        else if (instance == errorTextDisplay)
            updateErrorTextDisplay();
        else if (instance == helpContentGroup)
        {
            helpContentChanged = true;
            createHelpContent();
        }
        else if (instance == contentGroup)
        {
            contentGroup.addEventListener("errorStringChanged", contentGroup_errorStringdHandler, true);
        }
    }
    
    /**
     *  @private
     */
    override protected function partRemoved(partName:String, instance:Object) : void
    {
        super.partRemoved(partName, instance);
        
        // Remove the helpContent from the helpContentGroup so that it can be parented
        // by a different skin's helpContentGroup
        if (instance == helpContentGroup)
            helpContentGroup.removeAllElements();
        if (instance == contentGroup)
            contentGroup.removeEventListener("errorStringChanged", contentGroup_errorStringdHandler, true);
    }
    
    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private function updateErrorString():void
    {
        var uicElt:UIComponent;
        _elementErrorStrings = new Vector.<String>;
        
        for (var i:int = 0; i < numElements; i++)
        {
            uicElt = getElementAt(i) as UIComponent;
            
            if (uicElt)
            {
                if (uicElt.errorString != "")
                {
                    _elementErrorStrings.push(uicElt.errorString);
                }
            }
        }
        
        invalidateSkinState();
        
        updateErrorTextDisplay();
        dispatchEvent(new Event("elementErrorStringsChanged"));
    }
    
    /**
     *  Converts <code>elementErrorStrings</code> into a String, and assigns
     *  that String to the <code>errorTextDisplay</code> skin part for display. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.5
     *  @productversion Flex 4.5
     */
    protected function updateErrorTextDisplay():void
    {
        var msg:String = "";
        for (var i:int=0; i < elementErrorStrings.length; i++)
        {
            if (msg != "")
                msg += "\n";
            msg += elementErrorStrings[i];
        }
        
        if (errorTextDisplay)
            errorTextDisplay.text = msg;
        errorString = msg;
    }
    
    /**
     *  @private
     */
    private function createHelpContent():void
    {
        if (helpContentGroup)
            helpContentGroup.mxmlContent = _helpContent; 
    }
    
    //--------------------------------------------------------------------------
    //
    //  Event Handlers 
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private function contentGroup_errorStringdHandler(event:Event):void
    {
        updateErrorString();
    }
}
}