content - input controls like TextInputs, NumericSteppers, Sliders and ComboBoxes which are used to input the data for the form item.
error state - if the user has entered invalid data into the content, then the form item is put into the error state. To convey that a form item is in the error state, the form item might draw a red border around itself.
form button - a button in the form typically used to submit or cancel the form. A form button is usually lined up in one of the form columns.
form column - a form is laid out as a grid of columns and rows. Each form item corresponds to a row while each element in the form item corresponds to a column. The Form will vertically line up all elements in a column. The column is always wide enough to accommodate the widest element.
form item - generic term for a form element. Typically it corresponds to a row in the form. Examples of form items are a form button, a FormHeading, a FormItem or a divider.
FormItem - a form item class that contains a label and content.
help content - area that contains components that provides a description of the form item or instructions for filling it out.
horizontal layout - all form item elements are arranged in a horizontal line. Typically the label is on the left and the content on the right.
indicator - small icon that conveys a particular meaning for a form item. Typically this is a required indicator that denotes that the form content must be filled out.
label - text that names the form content. For example, a FormItem to select a state might have a form label of "State"
number label - displays the number of the form item if the form creates a numbered list out of the form items. For example, for the text "2. Last Name", "2." is the number label while "Last Name" is the label. Typically the number label and the label are separate elements so that each column lines up.
stacked layout - label element sits on top of the content. Content is arranged vertically.
ConstraintLayout - layout that supports advanced constraints
The Spark Form is a highly customizable container that supports modern form designs. As a SkinnableContainer, developers can easily customize the appearance and add visual states.
Form's layout will be based on the Spark Advanced Constraints layout. It should be fairly easy to provide tooling support for this component since layout is controlled almost entirely in a declarative manner. Form will support multiple, customized columns that can be ordered and positioned. It will support both a horizontal and stacked layout, the two most common form layouts.
Customizing the appearance and layout of the Form will be achieved by skinning. There will be a minimal amount of customization provided by styling.
With a Spark Form, developers can create richer form experiences. Help content provides context and instructions for the form item. Error states provide an opportunity to clearly illuminate the input error. For example, in an error state, the form item can have a red border or a light red background. Required items could have a light blue background, a bold label or a "(required)" text.
Using states and transitions, checking a CheckBox could slide open a whole new set of FormItem inputs.
Forms are everywhere in websites and applications. Axel is creating a registration form for the website PitchSpoon. The form contains required fields for a username, password, and email address. To indicate that these are required fields, he sets the required property of each of these FormItems. This displays a required asterisk in each field.
Axel is a bit rebellious and wants to change the layout of the form so that the labels appear on the right hand side of the content. He creates a copy of the FormItemSkin and modifies to which columns the content and label skin parts are relatively positioned. He also replaces the standard required indicator asterisk with a skull and crossbones icon by changing the source of the indicator image skin part.
Work with your Flash Builder contacts to complete a workflow document that describes how this feature will be exposed to users in Flash Builder. Include a link to this document here.
Work with your Catalyst contacts to complete a workflow specification that describes how this feature will be exposed to users in Catalyst. Include a link to this document here.
The Form feature includes a number of classes:
A Form usually has form item (FormItem, FormHeading) children. Its default skin is FormSkin, which contains a contentGroup skin part. The FormLayout is the default layout for this contentGroup. The FormItem's content are its children. Its skin is FormItemSkin, which has a contentGroup skin part. The FormItemLayout is the layout of the entire FormItemSkin, not just the contentGroup.
Form lays out it elements in a grid where each form item is a row and each element in a form item is a column. The Form sizes each column so that the widest column element can fit in the column. The Form vertically aligns the elements in each column.
Form - SkinnableContainer with form item and non-form item children.
FormItem - SkinnableContainer with content and multiple properties. The FormItem's children are the content and are placed in the contentGroup skin part. The FormItem container defines a number of skin parts, including labelDisplay, sequenceLabelDisplay, helpContentGroup, and errorTextDisplay. The content of these skin parts are specified in order by the label, sequenceLabel and helpContent properties.
FormHeading - simple form item component which contains a label. It is used to provide a heading to a set of form items. It is basically a FormItem without any content, indicator, sequenceLabel or helpContent. Since it is a separate class from FormItem, we can give it a default style (larger font size) and its own skin.
FormLayout - a VerticalLayout that has additional logic for laying out elements that have a FormItemLayout. The FormLayout asks each element with a FormItemLayout for its column widths by calling getMeasuredColumnWidths on the FormItemLayout. For each column, it calculates the largest measured width and stores that value in a max columns array. The FormLayout then passes this max columns array to each FormItemLayout by calling setLayoutColumnWidths.
FormItemLayout - a constraint-based grid layout similar to the mx Canvas grid layout. The FormItemLayout has a set of columns and rows. Each layout element can position its edges relative to each column and row. FormLayout uses two mx_internal functions (getMeasuredColumnWidths and setLayoutColumnWidths) to retrieve and set the column widths of each FormItem.
FormSkin - very similar to SkinnableContainerSkin except that the contentGroup's layout is FormLayout
FormItemSkin - horizontal layout for a FormItem. It contains 4 columns and 5 skin parts. The content column has width="100%", while the rest of the columns are content sized. If a content sized column is empty, it won't take any space in the FormItem. For example, if none of the FormItems specify a sequenceLabel, the sequenceLabel column width will be 0.
FormHeadingSkin - horizontal layout for a FormHeading. Contains the same 4 columns as the FormItemSkin. The label spans the second through fourth columns.
StackedFormItemSkin - stacked layout for a FormItem. It contains 3 columns and 5 skin parts. The first column is the sequenceLabel. The second column has width="100%" and contains the label, indicator and contentGroup. The third column contains the helpContent.
StackedFormHeadingSkin - stacked layout for a FormHeading. It contains the same 3 columns as the StackedFormItemSkin. The label spans the second and third columns.
In Spark, containers delegate their layout behavior to layout objects. Forms and FormItems also delegate their layout behavior to two layouts, FormLayout and FormItemLayout. These layouts are defined in their respective skins. FormLayout is the layout for the FormSkin's contentGroup, while the FormItemLayout is set to the FormItemSkin's layout property. These are different because the FormLayout lays out the children form items of the Form, while the FormItemLayout lays out the different skin parts of a FormItem.
FormLayout is a subclass of VerticalLayout and does not define the columns or the column widths. Instead, each of the FormItemLayouts defines a set of columns. The FormLayout interacts with each of the FormItemLayouts to ensure that the columns align with the columns of the other children. Since the form items will all use the same skin, they will have the same set of columns based on the FormItemLayout set in the skin. If form items have different skins, then each skin's FormItemLayout must have the same number of columns with the same "width" property settings for the columns to align properly. If there are a different number of columns, an RTE will be thrown. If the number of columns is the same but widths are different, then the behavior is undefined. Though FormLayout supports elements that are not form items, these elements will not participate in column alignment.
FormItemLayout defines the columns so that the form item skins can position their skin parts relative to these columns. Otherwise, the skins would not have easy access to the column definitions and the interaction between FormLayout and FormItemLayout would be more complex.
To align the columns, FormLayout first calculates the maximum column width for each FormItemLayout's set of columns. Each FormItemLayout then uses this set of maximum column widths as the column widths so that for a given column, the width will be the same in every FormItem. Columns that have percentWidth defined will be stretched or shrunk such that the columns fit within the Form's bounds.
In general, FormLayout respects the width property of each FormItem. If the aligned columns cause the content to be larger than a certain FormItem, the content will bleed outside the FormItem. The FormItem will not resize to match the aligned columns.
FormItemLayout subclasses ConstraintLayout, the spark equivalent of MX Canvas's Advanced Constraints feature. FormItemLayout will also implement the getMeasuredColumnWidths function which returns a vector of all the columns' measuredWidths, and the setLayoutColumnWidths function which will set each column's actual width. FormLayout uses these functions to align the FormItem columns. At the moment, ConstraintLayout will support a subset of the constraints possible including left, right, top, bottom, and baseline for columns and rows.
ConstraintLayout is a general grid layout that has a set of columns and rows defined to divide the layout target into regions. Layout elements can then align to the specified columns and rows using constraints. Layout elements will also be able to span multiple columns and rows in order to have good resizing behavior as well as flexibility in positioning.
The regions in a ConstraintLayout are defined by instances of ConstraintColumn and ConstraintRow in ConstraintLayout's constraintColumns and constraintRows properties. Each ConstraintColumn/Row can have a fixed pixel size (explicitWidth/Height), a percent size (percentWidth/Height), or no size defined (content size). A fixed size column will not resize regardless of how the layout target resizes or what content is placed in the column. A content size column will size to fit all of its content. If a component spans across content size columns, the space will be distributed evenly across those columns. Percent size columns will fill the remaining space based on its percentage compared to the total percentage of all the columns.
The per-element constraints supported by ConstraintLayout will be left, right, top, bottom, and baseline
. Each element may specify constraints with respect to the parent container (left="5"
) or to a column/row (left="col1:5"
). The left, right, top
, and bottom
constraints define how far the edges of the element are offset from the edge of the container or region. The baseline
constraint defines the offset between the baseline defined by the constraint row (or the top of the container) and the baseline position of the element.
To address the issue of aligning elements with variable baseline positions in the same row to the same baseline, ConstraintRows will have a new property, baseline.
The ConstraintRow baseline can be specified as a number or with respect to the max ascent of the row, "maxAscent:x"
. The default value is "maxAscent:0"
. Elements with a baseline constraint that specifies a row will be aligned based on this baseline property on the row.
If the baseline value on the row is only a number, the elements contained by the row will be positioned that many pixels below the top of the container. If the baseline value is defined with respect to the max ascent of the row, the elements will be first aligned to the baseline position of the element with the largest baseline position. Then, the elements will be offset from that new position.
Here is a simple example where we have 4 Labels in a Group. Each Label is constrained with baseline to "row1" with an offset of 0 except for the first one which has an offset of 10. Because "row1" has the default baseline property of "maxAscent:0", they are aligned at the baseline position of the largest label. The first label is also offset from the baseline by 10 pixels.
<s:Group y="200" x="30">
<s:layout>
<s:FormItemLayout>
<s:constraintRows>
<s:ConstraintRow id="row1" />
<s:ConstraintRow id="row2" baseline="maxAscent:5"/>
<s:ConstraintRow id="row3" baseline="30"/>
</s:constraintRows>
</s:FormItemLayout>
</s:layout>
<s:Label fontSize="20" text="hello!" baseline="row1:10" x="38" color="blue"/>
<s:Label fontSize="25" text="hello!" baseline="row1:0" x="89" color="#FF9933"/> <!-- orange -->
<s:Label fontSize="35" text="hello!" baseline="row1:0" x="149" color="green"/>
<s:Label fontSize="50" text="hello!" baseline="row1:0" x="234" />
</s:Group>
If the labels were placed in "row2", then the labels would be aligned, but positioned 5 pixels lower since the baseline of "row2" is "maxAscent:5". If the labels were placed in "row3", the labels would be positioned with their baseline positions at exactly 30 pixels from the top of the container.
The new baselinePositionElement property on Group and SkinnableContainer allow a developer to specify which element is used to calculate the baselinePosition. The element can be any descendant of the Group or SkinnableContainer. If an element is specified, then the Group or SkinnableContainer's baselinePosition is equivalent to the sum of the baselinePositionElement's baselinePosition and y properties. If no value is specified, then the baselinePosition is calculated using the first element. Typically in most layouts, this element will be the topmost element. This property provides flexibility in deciding how to align a Group or SkinnableContainer with other components and containers.
General:
left
and right
) are set to reference a column/row, the element will not count towards their sizes.Measuring Content Size Columns and Rows:
The calculations for measuring a content size column or row. For each element, we calculate the size of each content size column and row that it is constrained to, then we set the size of each content size column and row to the maximum of these calculated sizes. The baseline constraint only affects rows. || || Content Size (spans 1) || Content Size (spans both) || Mix of fixed and content size || | no constraints | 0 | 0 | 0 | | constrained to parent only | 0 | 0 | 0 | | basic (one, e.g. just left) | preferredSize + constraint | N/A | preferredSize + constraint - fixedSizes | | basic (both) | preferredSize + constraints | (preferredSize + constraints)/# of regions | (preferredSize + constraints - fixedSizes)/# of regions | | baseline only or with top (rows only) | preferredHeight + baseline - baselinePosition | N/A | preferredHeight + baseline - fixedSizes - baselinePosition | | baseline with top, bottom (rows only) | preferredHeight + constraints | (preferredHeight + constraints)/# of regions | (preferredHeight + constraints - fixedSizes)/# of regions |
Constraints Precedence Order:
FormItem is a SkinnableContainer, thus it has a layout property. This layout property is a proxy to the layout property of the contentGroup skin part. The contentGroup holds the components used to input data, like TextInputs and ComboBoxes. By default, the contentGroup's layout is VerticalLayout. The FormItemSkin itself uses a FormItemLayout to position the contentGroup and all of the other skin parts like labelDisplay, sequenceLabelDisplay, indicator and helpContentGroup.
There is potential for confusion between the usage of these two layouts. Do we need to modify the API to clarify the distinction?
The only requirement to participate in the column alignment behavior of FormLayout is that the element's layout is a FormItemLayout. To create a custom form item like a form submit button, add a Group as an element of the Form. The Group should have a FormItemLayout layout which contains the same number of constraint columns as the rest of the form item elements. The submit button is then added as an element of the Group and positioned relative to one of the constraint columns. The Examples section has an example of this use case.
The FormItem can be in either the normal or required state and either the disabled or error state. The possible states are:
The error state occurs when the FormItem's content is INVALID (it has failed validation). The required state occurs when the FormItem's required property is set to true. This denotes that the FormItem's content should be filled out before submitting the form.
A FormItem will listen for the FlexEvent.VALID and FlexEvent.INVALID validation events on each of its content elements. The elementErrorStrings property contains a Vector of the errorStrings (if any) of the content elements. By default, the FormItem will turn off the error toolTip and red error border of each content element so that it can be responsible for displaying the errors. It will use the new UIComponent showErrorTip and showErrorSkin inheriting styles.
Additions to MXML Language and ActionScript Object Model
Include the relevant API for this feature using the following example as a guideline. Make sure you indicate whether APIs are public or protected. You do not need to present private or mx_internal APIs here.
package spark.components
{
/**
* SkinnableContainer with form item children. It uses the FormLayout layout by default
*/
public class Form extends SkinnableContainer
{
public function Form()
{
}
}
}
\
package spark.components
{
/**
* SkinnableContainer with content and multiple properties. The FormItem's children
* are the content and are placed in the contentGroup skin part. The FormItem
* container defines a number of skin parts, including labelDisplay,
* sequenceLabelDisplay, and helpContentGroup. The content of these skin parts are
* specified in order by the label, sequenceLabel and helpContent properties.
*/
public class FormItem extends SkinnableContainer
{
public function FormItem()
{
}
/**
* Displays this FormItem's label.
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var labelDisplay:TextBase;
/**
* A reference to the visual element that displays the FormItem's sequenceLabel.
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var sequenceLabelDisplay:TextBase;
/**
* A reference to the group that contains the FormItem's helpContent
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var helpContentGroup:Group;
/**
* A reference to the visual element that display the FormItem content errorStrings.
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var errorTextDisplay:TextBase;
/**
* Content that provides a description of the form item or instructions
* for filling it out.
*
* @default undefined
*/
public function get helpContent():IDeferredInstance;
public function set helpContent(value:IDeferredInstance):void;
/**
* Text that names the form content. For example, a FormItem to
* select a state might have a form label of "State"
*
* @default ""
*/
public function get label():String;
public function set label(value:String):void;
/**
* The number of the form item if the form creates a
* numbered list out of the form items
*
* @default ""
*/
public function get sequenceLabel():String;
public function set sequenceLabel(value:String):void;
/**
* If true, puts the FormItem skin into the
* required state. By default, this displays
* an indicator that the FormItem children require user input.
* If false, indicator is not displayed.
*
*
This property controls skin's state only.
* You must attach a validator to the children
* if you require input validation.
*
* @default false
*/
public function get required():Boolean;
public function set required(value:Boolean):void;
/**
* Each vector item contains the error string from a content element.
* If none of the content elements are invalid, then the vector will
* be empty.
*/
public var elementErrorStrings:Vector. = new Vector.;
}
}
\
package spark.components
{
/**
* A simple form item component which contains a label. It is used to
* provide a heading to a set of form items. It is basically a FormItem
* without any content, sequenceLabel or helpContent. Since it is
* a separate class from FormItem, it has a default style
* (larger font size) and its own skin.
*/
public class FormHeading extends SkinnableComponent
{
public function FormHeading()
{
}
/**
* Displays this FormItem's label.
*/
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a>
public var labelDisplay:TextBase;
/**
* Text to display in the FormHeading
*/
public function get label():String;
public function set label(value:String):void;
}
}
\
package spark.layouts
{
/**
* The ConstraintLayout class arranges the layout elements according to their
* individual settings, independent of each-other. Layout elements can be
* constrained to both the container and the regions defined in the
* constraintColumns and constraintRows properties.
*
*
Per-element supported constraints are left, right,
* top, bottom, baseline,
* percentWidth, and percentHeight. Element's minimum
* and maximum sizes will always be respected.
*
* The regions of the container are defined by the ConstraintColumn and
* ConstraintRow class instances found in the constraintColumns and constraintRows
* properties. They are laid out in index order left-to-right, top-to-bottom,
* starting from the origin of the container. The regions may either have fixed
* sizes or content sizes. Fixed size means that the region will occupy that many
* pixels of space. Fixed size is specified by an explicit width or height on the
* region. Content size means that the space taken up by the region is dictated by
* the elements which are constrained to that region. If the elements span more
* than one region, the space is divided evenly between the regions. Content
* size is specified by not setting the size of a region.
*/
public class ConstraintLayout extends LayoutBase
{
public function ConstraintLayout();
/**
* A Vector of ConstraintColumn instances that partition this container.
* The ConstraintColumn instance at index 0 is the left-most column;
* indices increase from left to right. The vector is defensively copied
* for both setting and getting. constraintColumns will return an empty
* vector if no columns exist even if it is set to null.
*/
public function get constraintColumns():Vector.;
public function set constraintColumns(value:Vector.):void;
/**
* A Vector of ConstraintRow instances that partition this container.
* The ConstraintRow instance at index 0 is the top-most row;
* indices increase from top to bottom. The vector is defensively copied
* for both setting and getting. constraintRows will return an empty
* vector if no rows exist even if it is set to null.
*/
public function get constraintRows():Vector.;
public function set constraintRows(value:Vector.):void;
/**
* Used by FormItemLayout to measure and set new column widths
* before laying out the elements. This function performs
* a measure pass on both the columns and the rows to determine
* their widths and heights.
*/
protected function measureAndPositionColumnsAndRows():void;
/**
* Lays out the elements of the layoutTarget using the current
* widths and heights of the columns and rows. Used by FormItemLayout
* to set new column widths and then layout elements using those new widths.
*/
protected function layoutContent(unscaledWidth:Number, unscaledHeight:Number):void;
}
}
\
package spark.layouts
{
public class FormItemLayout extends ConstraintLayout
{
public function FormItemLayout()
{
}
/**
* Used by layout to get the measured column widths
*/
public function getMeasuredColumnWidths():Vector.;
/**
* @private
* Used by layout to set the column widths for updateDisplayList. Must
* call this if you want to override the default widths of the columns.
*/
public function setLayoutColumnWidths(value:Vector.):void;
}
}
\
package spark.layouts
{
/**
* The default layout for Spark Form skins.
*/
public class FormLayout extends VerticalLayout
{
public function FormLayout()
{
super();
}
}
}
\
package mx.core
{
public class UIComponent
{
/**
* If true, then show the error tip on a roll over if the errorString is set.
*
* @default true
*/
<a href="Style%28name%3D%26quot%3BshowErrorTip%26quot%3B%2C%20type%3D%26quot%3BBoolean%26quot%3B%2C%20inherit%3D%26quot%3Byes%26quot%3B%29">Style(name="showErrorTip", type="Boolean", inherit="yes")</a>
/**
* If true, then show the error skin if the errorString is set.
*
* @default true
*/
<a href="Style%28name%3D%26quot%3BshowErrorSkin%26quot%3B%2C%20type%3D%26quot%3BBoolean%26quot%3B%2C%20inherit%3D%26quot%3Byes%26quot%3B%29">Style(name="showErrorSkin", type="Boolean", inherit="yes")</a>
public function UIComponent()
{
}
}
}
\
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
alpha.disabled=".5">
<!-- host component -->
<fx:Metadata>
<![CDATA[
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
<a href="HostComponent%28%26quot%3Bspark.components.FormItem%26quot%3B%29">HostComponent("spark.components.FormItem")</a>
]]>
</fx:Metadata>
<fx:Style>
.labelStyle
{
fontWeight : "bold";
color : #333333;
}
.helpTextStyle
{
fontStyle : "italic";
color : #666666;
}
.errorTextStyle
{
fontStyle : "italic";
color : #FE0000;
}
</fx:Style>
<s:states>
<s:State name="normal" />
<s:State name="disabled" stateGroups="disabledStates"/>
<s:State name="error" stateGroups="errorStates"/>
<s:State name="required" stateGroups="requiredStates"/>
<s:State name="requiredAndDisabled" stateGroups="requiredStates, disabledStates"/>
<s:State name="requiredAndError" stateGroups="requiredStates, errorStates"/>
</s:states>
<s:layout>
<s:FormItemLayout>
<s:constraintColumns>
<s:ConstraintColumn id="sequenceCol" />
<s:ConstraintColumn id="labelCol" />
<s:ConstraintColumn id="contentCol"/>
<s:ConstraintColumn id="helpCol"/>
</s:constraintColumns>
<s:constraintRows>
<s:ConstraintRow id="row1" baseline="maxAscent:4"/>
</s:constraintRows>
</s:FormItemLayout>
</s:layout>
<s:Label id="sequenceLabelDisplay" styleName="labelStyle"
left="sequenceCol:4" right="sequenceCol:4"
bottom="row1:4"
baseline="row1:0"/>
<s:Label id="labelDisplay" styleName="labelStyle"
left="labelCol:0" right="labelCol:5"
bottom="row1:4"
baseline="row1:0"/>
<!-- Don't show the error tip on the content elements -->
<s:Group id="contentGroup" showErrorTip="false" showErrorSkin="true"
left="contentCol:0" right="contentCol:1"
baseline="row1:0" bottom="row1:4">
<s:layout>
<s:VerticalLayout/>
</s:layout>
</s:Group>
<mx:Image source="@Embed(source='assets/RequiredIndicator.png')"
source.errorStates="@Embed(source='assets/ErrorIndicator.png')"
toolTip="Required" toolTip.errorStates=""
includeIn="requiredStates,errorStates" scaleContent="false"
baseline="row1:0"
x="{contentGroup.x + contentGroup.contentWidth + 3}"/>
<s:Group id="helpContentGroup" styleName="helpTextStyle" excludeFrom="errorStates"
left="helpCol:27" right="helpCol:4"
top="row1:4" bottom="row1:4"/>
<s:RichText id="errorTextDisplay" styleName="errorTextStyle" includeIn="errorStates"
left="helpCol:27" right="helpCol:4"
bottom="row1:4"
baseline="row1:0" maxDisplayedLines="-1"/>
</s:Skin>
\
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
alpha.disabled=".5">
<!-- host component -->
<fx:Metadata>
<![CDATA[
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
<a href="HostComponent%28%26quot%3Bspark.components.FormItem%26quot%3B%29">HostComponent("spark.components.FormItem")</a>
]]>
</fx:Metadata>
<fx:Style>
.labelStyle
{
fontWeight : "bold";
color : #333333;
}
.helpTextStyle
{
fontStyle : "italic";
color : #666666;
}
.errorTextStyle
{
fontStyle : "italic";
color : #FE0000;
}
</fx:Style>
<s:states>
<s:State name="normal" />
<s:State name="disabled" stateGroups="disabledStates"/>
<s:State name="error" stateGroups="errorStates"/>
<s:State name="required" stateGroups="requiredStates"/>
<s:State name="requiredAndDisabled" stateGroups="requiredStates, disabledStates"/>
<s:State name="requiredAndError" stateGroups="requiredStates, errorStates"/>
</s:states>
<s:layout>
<s:FormItemLayout>
<s:constraintColumns>
<s:ConstraintColumn id="sequenceCol" />
<s:ConstraintColumn id="contentCol"/>
<s:ConstraintColumn id="helpCol"/>
</s:constraintColumns>
<s:constraintRows>
<s:ConstraintRow id="row1" baseline="maxAscent:4"/>
<s:ConstraintRow id="row2"/>
</s:constraintRows>
</s:FormItemLayout>
</s:layout>
<s:Label id="sequenceLabelDisplay" styleName="labelStyle"
left="sequenceCol:4" right="sequenceCol:5"
bottom="row1:5" baseline="row1:0"/>
<s:Label id="labelDisplay" styleName="labelStyle"
left="contentCol:0" right="helpCol:4"
bottom="row1:5" baseline="row1:0"/>
<s:Group id="contentGroup"
showErrorTip="false" showErrorSkin="true"
left="contentCol:0" right="contentCol:27"
top="row2:0" bottom="row2:4" baseline="row2:0">
<s:layout>
<s:VerticalLayout/>
</s:layout>
</s:Group>
<mx:Image source="@Embed(source='assets/RequiredIndicator.png')"
source.errorStates="@Embed(source='assets/ErrorIndicator.png')"
toolTip="Required" toolTip.errorStates="{hostComponent.errorString}"
includeIn="requiredStates,errorStates" scaleContent="false"
baseline="row2:0"
x="{contentGroup.x + contentGroup.contentWidth + 3}"/>
<s:Group id="helpContentGroup" styleName="helpTextStyle"
left="helpCol:27" right="helpCol:4" top="row2:0" bottom="row2:4"/>
</s:Skin>
Example showing a Form in both horizontal and stacked layout:
<?xml version="1.0" encoding="utf-8"?>
<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"
xmlns:form="formPrototype.*" width="1100" height="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
<a href="Bindable">Bindable</a>
public var statesDP:ArrayCollection = new ArrayCollection(["Arizona","California","Kansas",
"New Mexico","Texas","Wyoming"]);
]]>
</fx:Script>
<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
#stackedForm s|FormItem
{
skinClass : ClassReference("spark.skins.spark.StackedFormItemSkin");
}
</fx:Style>
<s:layout>
<s:HorizontalLayout paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10"/>
</s:layout>
<s:Form>
<s:FormHeading label="Horizontal"/>
<s:FormItem label="Address" sequenceLabel="1." required="true" >
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:helpContent>
<s:Label text="(ex. 123 Main Street)" baseline="24"/>
<s:Button label="?" width="30" baseline="24" x="120"/>
</s:helpContent>
</s:FormItem>
<s:FormItem label="City" sequenceLabel="2." required="true">
<s:TextInput width="100%"/>
</s:FormItem>
<s:FormItem label="State" sequenceLabel="3.">
<s:ComboBox dataProvider="{statesDP}" width="100%"/>
</s:FormItem>
<s:FormItem label="ZipCode" sequenceLabel="4." required="true">
<s:TextInput widthInChars="4" restrict="0123456789"/>
<s:helpContent>
<s:Label text="Will appear in your profile" left="0" right="0" baseline="24"/>
</s:helpContent>
</s:FormItem>
</s:Form>
<s:Form id="stackedForm">
<s:FormItem label="Address" sequenceLabel="1." required="true" >
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:helpContent>
<s:Label text="(ex. 123 Main Street)" baseline="24"/>
<s:Button label="?" width="30" baseline="24" x="120"/>
</s:helpContent>
</s:FormItem>
<s:FormItem label="City" sequenceLabel="2." required="true">
<s:TextInput width="100%"/>
</s:FormItem>
<s:FormItem label="State" sequenceLabel="3.">
<s:ComboBox dataProvider="{statesDP}" width="100%"/>
</s:FormItem>
<s:FormItem label="ZipCode" sequenceLabel="4." required="true">
<s:TextInput widthInChars="4" restrict="0123456789"/>
<s:helpContent>
<s:Label text="Will appear in your profile" left="0" right="0" baseline="24"/>
</s:helpContent>
</s:FormItem>
</s:Form>
</s:Application>
\
Example showing how FormItem validation hooks up:
<?xml version="1.0" encoding="utf-8"?>
<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" minWidth="955" minHeight="600">
<fx:Declarations>
<mx:PhoneNumberValidator id="pnv" source="{ti_phone}" property="text"/>
<mx:ZipCodeValidator id="zcv" source="{ti_zip}" property="text"/>
</fx:Declarations>
<s:Form width="500" >
<s:FormItem label="Phone" sequenceLabel="1." required="true">
<s:TextInput id="ti_phone" width="100%"/>
</s:FormItem>
<s:FormItem label="Zip" sequenceLabel="2." required="true">
<s:TextInput id="ti_zip" widthInChars="4" restrict="0123456789"/>
</s:FormItem>
</s:Form>
</s:Application>
\
Example showing how a submit button is added to a Form:
<?xml version="1.0" encoding="utf-8"?>
<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" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
public function submitForm():void
{
// Handle Form submission logic here
}
]]>
</fx:Script>
<s:Form width="500" defaultButton="{submitBtn}">
<s:FormItem label="Phone" sequenceLabel="1." required="true">
<s:TextInput id="ti_phone" width="100%"/>
</s:FormItem>
<s:FormItem label="Zip" sequenceLabel="2." required="true">
<s:TextInput id="ti_zip" widthInChars="4" restrict="0123456789"/>
</s:FormItem>
<s:Group>
<s:layout>
<s:FormItemLayout>
<s:constraintColumns>
<s:ConstraintColumn id="sequenceCol" />
<s:ConstraintColumn id="labelCol" />
<s:ConstraintColumn id="contentCol"/>
<s:ConstraintColumn id="helpCol"/>
</s:constraintColumns>
<s:constraintRows>
<s:ConstraintRow id="row1"/>
</s:constraintRows>
</s:FormItemLayout>
</s:layout>
<s:Button id="submitBtn" label="Submit" click="submitForm()" left="helpCol:2"/>
</s:Group>
</s:Form>
</s:Application>
\
Example showing how a FormItemGroup is used to create two distinctive groupings (B-Feature):
<?xml version="1.0" encoding="utf-8"?>
<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" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
<a href="Bindable">Bindable</a>
public var statesDP:ArrayCollection = new ArrayCollection(["Arizona","California","Kansas",
"New Mexico","Texas","Wyoming"]);
]]>
</fx:Script>
<s:Form>
<s:FormItemGroup label="Billing Address">
<s:FormItem label="Address" required="true">
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:helpContent>
<s:Label text="ex. 123 Main Street"/>
</s:helpContent>
</s:FormItem>
<s:FormItem label="City" required="true" >
<s:TextInput width="100%"/>
</s:FormItem>
<s:FormItem label="State">
<s:ComboBox dataProvider="{statesDP}" width="100%"/>
</s:FormItem>
<s:FormItem label="ZipCode" required="true">
<s:TextInput widthInChars="4" restrict="0123456789"/>
<s:helpContent>
<s:Label text="Will appear in your profile"/>
</s:helpContent>
</s:FormItem>
</s:FormItemGroup>
<s:CheckBox label="Use Billing Address?" id="useBilling"/>
<s:FormItemGroup label="Shipping Address" enabled="{useBilling.selected}">
<s:FormItem label="Address" required="true">
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:TextInput width="100%"/>
<s:helpContent>
<s:Label text="ex. 123 Main Street"/>
</s:helpContent>
</s:FormItem>
<s:FormItem label="City" equired="true">
<s:TextInput width="100%"/>
</s:FormItem>
<s:FormItem label="State">
<s:ComboBox dataProvider="{statesDP}" width="100%"/>
</s:FormItem>
<s:FormItem label="ZipCode" required="true">
<s:TextInput widthInChars="4" restrict="0123456789"/>
<s:helpContent>
<s:Label text="Will appear in your profile"/>
</s:helpContent>
</s:FormItem>
</s:FormItemGroup>
</s:Form>
</s:Application>
\
Example showing how to retrieve values from controls inside of the Form (B-Feature):
<?xml version="1.0" encoding="utf-8"?>
<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"
xmlns:form="formPrototype.*" width="1100" height="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
<a href="Bindable">Bindable</a>
public var statesDP:ArrayCollection = new ArrayCollection(["Arizona","California","Kansas",
"New Mexico","Texas","Wyoming"]);
public function submit_clickHandler(event:MouseEvent):void
{
var addressVO:Object = addressForm.getValueObject();
/* Example output
address = 123 Main Street
city = Chicago
state = Illinois
zipCode = 60661
*/
for (var name:* in addressVO)
{
trace(name + " = " + addressVO<a href="name">name</a>);
}
}
]]>
</fx:Script>
<s:layout>
<s:HorizontalLayout paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10"/>
</s:layout>
<s:Form id="addressForm" defaultButton="{submitBtn}">
<s:FormItem label="Address" required="true">
<s:TextInput width="100%" id="address"/>
<s:helpContent>
<s:Label text="ex. 123 Main Street"/>
</s:helpContent>
</s:FormItem>
<s:FormItem label="City" required="true" id="city>
<s:TextInput width="100%"/>
</s:FormItem>
<s:FormItem label="State">
<s:ComboBox dataProvider="{statesDP}" width="100%" id="state"/>
</s:FormItem>
<s:FormItem label="ZipCode" required="true">
<s:TextInput widthInChars="4" restrict="0123456789" id="zipCode"/>
<s:helpContent>
<s:Label text="Will appear in your profile"/>
</s:helpContent>
</s:FormItem>
<s:Button label="Submit" click="submit_clickHandler(event)"/>
</s:Form>
</s:Application>
Enter implementation/design details for the feature here. This section may be updated after the spec signs off.
Will this feature require any compiler changes? If so, describe here, if not already included in the Detailed Description.
None
None
None
How do we present the validation errors on the FormItem? How do we "label" each column?
None
Do we need different FormItemSkins for different locales? Or is mirroring sufficient?
None
List the RTE message strings that will require localization. (They must all come from .properties files.)
List all UI text that will require localization, such as the month and day names in a DateChooser. (All such text must all come from .properties files.)
List all UI images, skins, sounds, etc. that will require localization.
Discuss any locale-specific formatting requirements for numbers, dates, currency, etc.
Discuss any locale-specific sorting requirements.
N/A
Data structure for Validation errors
Instead of a Vector of Strings for the elementErrorStrings property, should we expose both the invalid component and the error string? Perhaps a map with the component as a key and the string as a value?
Interaction between FormLayout and FormItemLayout
Should the FormLayout.columnWidths be stored on Form instead? And should FormItem store the measuredColumnWidths and layoutColumnWidths?
The consensus is that keeping these properties on the layouts simplifies the interaction. Form items no longer need to implement the IFormItem interface. FormItem, FormHeading and other form item classes no longer need to duplicate the proxy logic for the column width properties.
FormLayout will look at the layout of each element. If that layout is a FormItemLayout, then FormLayout will align the columns of that form item.
Should FormLayout or FormItemLayout contain the constraints?
It wouldn't make sense for FormLayout to contain the row constraints. This would require FormLayout to declare a new row constraint for each form item. Since the FormItem's skin parts are positioned relative to the columns, the columns should be declared in the FormItemSkin.
Change helpTextElement to a content group?
Instead of having a helpText, XD mentioned wanting to put arbitrary components next to the FormItem content. Examples include a help icon or a password strength indicator. To implement this, should the helpContent skinpart be similar to contentGroup or to the viewport of a Scroller?