Menu

Spark Group

SourceForge Editorial Staff

Spark Group - Functional and Design Specification


Glossary


graphic element - A graphical element like a Rectangle, Path, or Bitmap. This element is not a subclass of DisplayObject; however, it does need a DisplayObject to render on to.

visual element - A visual element (a.k.a. - "element"). This can be a halo component, a gumbo component, or a graphic element. Visual elements implement IVisualElement.

data item (a.k.a. - "item") - Basically anything is considered a data item. Mostly it refers to a non-visual item like a String, Number, XMLNode, etc. A visual element can also be a data item--it just depends on how it's treated.

Summary and Background


Group is the simple container base class in Gumbo. It extends GroupBase, which extends UIComponent and is designed to be flexible and lightweight.

Groups can contain only visual elements. Layout is handled by a separate layout object, so that the Group just delegates measure() and updateDisplayList() calls to this layout object. This is different from Halo where containment and layout were baked into the Container, List, and other individual components.

In Gumbo, there will be two light-weight container classes: one for visual elements, Group, and one for data items, DataGroup. DataGroup has an extra step where it takes the data items and converts them to visual elements. In both Group and DataGroup, layout will be controlled by a separate layout object. The DataGroup spec is written up separately.

There are several key differences between the Group class and the Halo Container class:

  • Swappable layouts. In Group, layout is handled by a separate object which can be changed at run-time. In Container, layout is baked into the Container subclass.
  • Group does not have any chrome, rawChildren or contentPane. If you want a border around a Group, it must be composited together separately (check out SkinnableContainer).
  • Group does not automatically show scrollbars. Scrolling is handled by Spark Scroller.
  • Group does not implement deferred instantiation. It is implemented at the SkinnableContainer level.

Usage Scenario


Example 1: Button Skin

<s:Skin xmlns...>

    <!-- This group is the background graphics for the button skin. -->
    <s:Group left="0" right="0" top="0" bottom="0">
        <s:Rect radiusX="4" radiusY="4" left="0" top="0" right="0" bottom="0">
            <s:stroke> ... </stroke>
            <s:fill> ... </fill>
        </s:Rect>
        <s:Rect radiusX="3" radiusY="3" left="1" top="1" right="1" bottom="1">
            <s:fill> ... </fill>
        </s:Rect>
    </s:Group>

    <s:TextBox horizontalCenter="0" verticalCenter="0" text="{hostComponent.label}" />
</s:Skin>

Example 2: Complex Panel Skin

<s:Skin xmlns...>

    <s:TextBox id="header" left="0" right="0" top="0" height="30" />

    <!-- This group is the main contentGroup for the panel. The 
         Panel will push its content into this group  -->
    <s:Group id="contentGroup" left="5" top="35" right="5" bottom="35">
        <s:layout><s:VerticalLayout /></s:layout>
    </s:Group>

    <!-- This group is the footer for the Panel. -->
    <s:TextBox id="footer" left="0" right="0" bottom="0" height="30" />
</s:Skin>

Detailed Description


Content

A Group represents a mutable ordered list of visual elements. Elements are managed through a "content API" with methods that are similar to the ones defined on DisplayObjectContainer-- addElement(), addElementAt(), removeElementAt(), etc….

Here are the presentation rules for instantiation a component:

  1. IGraphicElement - used directly. Group intelligently creates DisplayObjects as needed and calls the draw() method on the element when needed.
  2. IVisualElement - used directly.

The content APIs (addElement(), removeElement(), etc…) must be used to manipulate the content items. The content APIs mimic the child management APIs in DisplayObjectContainer.

[[ note ]]
There's also a mxmlContent property defined on Group which is an Array. However, its use is discouraged. It can be used to initialize a group's visual elements (or do a wholesale replace of them). The mxmlContent property is the default property of Group. However, if you put the same element into two different mxmlContent arrays, it will fail. Using the content APIs is safer because these methods won't let you get into an inconsistent state. Also, once mxmlContent is set, you shouldn't modify this Array; you should use the "content API" instead. For this reason, using mxmlContent directly is discouraged.
[[ /note ]]

As noted earlier, a visual element is an IVisualElement which is either a UIComponent or a GraphicalElement. The Group implementation converts graphical elements to DisplayObjects, mapping many graphical elements to one DisplayObject when that's possible. The developer should always use the content APIs defined on Group (addElement(), removeElement(), ...) and not the ones defined on DisplayObjectContainer (addChild(), removeChild(), ...). Group supports adding non-display objects as children--namely GraphicElements. The Flash child manipulation APIs (addChild(), etc…) don't support this, and they are typed to accept DisplayObjects only.

You can use the DisplayObjectContainer's numChildren and getChildAt() APIs to iterate the flash display object children. The APIs that add, remove or shuffle children should not be called, and will throw an RTE if they are called. This is because, under the hood, Group may map many graphical elements to a single DisplayObject, so the display list should only be manipulated by the Group itself and not by any outside developer.

Group has a layout property that can be set to change how the visual elements are laid out. Moreover, for ease of use, there is an HGroup and a VGroup subclass of Group. These subclasses pre-set their layouts to horizontal and vertical respectively. In addition, properties on the layout, like gap, verticalAlign, columnCount, rowCount, etc… are proxied up to the HGroup and VGroup as appropriate.

Graphic Elements

The Group class supports graphic elements as content. Group is responsible for allocating backing display objects when needed, and calling the draw() method on the graphic elements. The Group class supports all of the attributes of the FXG Group tag described in the FXG 1.0 specification.

Layout

Layout and measurement are handled by a separate object, which can be changed dynamically at runtime. Details about layout management are in the layout spec. The important parts to know are:

  • Measurement and layout are done by the layout object, not the Group. This means Group delegates the calls to measure() and updateDisplayList() to this layout object.
  • Custom layouts can be written
  • Horizonal, vertical, and basic (absolute) layout code will be provided.
  • There will be a mechanism for setting layout-specific properties for a component (eg horizontalGap).

Clipping and Scrolling

Clipping and scrolling are supported by the clipAndEnableScrolling, horizontalScrollPosition, verticalScrollPosition, contentWidth, and contentHeight properties. Display and management of scrollbars will be done by Scroller. See the Spark Viewport spec for more info.

Masking is supported by the DisplayObject mask property.

API Description


For implementation purposes, GroupBase is an base class for Group and DataGroup so that code isn't duplicated across both classes. It's essentially an abstract base class with some implementations for common methods.

package mx.components.baseClasses
{

class GroupBase extends UIComponent implements IViewport
{

/**
 *  Layout for this Group.
 *
 *  The layout object is responsible for measurement
 *  and layout of the Group contents.
 *
 *  @default mx.layout.BasicLayout
 *
 *  @see LayoutBase
 */
public function get layout():LayoutBase;
public function set layout(value:LayoutBase):void;

/**
 *  The ResizeMode for this container.  If the resize mode
 *  is set to <code>ResizeMode.NORMAL</code>, resizing is done by laying 
 *  out the children with our new width and height.  If the 
 *  resize mode is set to <code>ResizeMode.SCALE</code>, all of the children 
 *  keep their unscaled width and height and the children 
 *  are scaled to change size.
 * 
 * @default ResizeMode.NORMAL
 * 
 * @see mx.components.ResizeMode
 */
public function get resizeMode():String
public function set resizeMode(stringValue:String):void

/**
 *  If <code>true</code>, measurement and layout are done
 *  when the position or size of a child is changed.
 *  If <code>false</code>, measurement and layout are done only once,
 *  when children are added to or removed from the container.
 *
 *  @default true
 */
public function get autoLayout():Boolean
public function set autoLayout(value:Boolean):void;

//--------------------------------------------------------------------------
//
//  ViewPort/Scrolling Code (see Scroller spec)
//  
//
//--------------------------------------------------------------------------

public function get horizontalScrollPosition():Number;
public function set horizontalScrollPosition(value:Number):void;

public function get verticalScrollPosition():Number;
public function set verticalScrollPosition(value:Number):void;

public function get clipAndEnableScrolling():Boolean;
public function set clipAndEnableScrolling(value:Boolean):void;

public function get contentWidth():Number;

public function get contentHeight():Number;

public function getHorizontalScrollPositionDelta(scrollUnit:uint):Number;

public function getVerticalScrollPositionDelta(scrollUnit:uint):Number;

//--------------------------------------------------------------------------
//
//  Properties: IGraphicElement
//  See FXG spec: http://opensource.adobe.com/wiki/display/flexsdk/FXG+1.0+Specification
//
//--------------------------------------------------------------------------

public function get maskType():String;
public function set maskType(value:String):void;

//--------------------------------------------------------------------------
//
//  Layout element iteration
//
//  Iterator methods used by Layout objects. For visual elements, the layout element
//  is the element itself. For data items, the layout element is the item renderer
//  instance that is associated with the item.
//
//  These methods and getters are really abstract methods that are 
//  implemented in Group and DataGroup.  We need them here in BaseGroup 
//  so that layouts can use these methods.
//
//  <code>getElementIndex()</code> isn't actually needed for layout, but it's
//  implemented in both Group and DataGroup.
//--------------------------------------------------------------------------

/**
 *  The number of visual elements in this visual container.
 * 
 *  @return The number of elements in this visual container
 */
function get numElements():int;

/**
 *  Returns the visual element that exists at the specified index.
 *
 *  @param index The index of the element to retrieve.
 *
 *  @return The element at the specified index.
 * 
 *  @throws RangeError If the index position does not exist in the child list.
 */ 
function getElementAt(index:int):IVisualElement

/**
 *  Returns the index position of a visual element.
 *
 *  @param element The element to identify.
 *
 *  @return The index position of the element to identify.
 * 
 *  @throws ArgumentError If the element is not a child of this visual container.
 */ 
function getElementIndex(element:IVisualElement):int;

}
}

\
\

package mx.events
{

/**
 *  Represents events that are dispatched when an element of a Group
 *  is created or destroyed. 
 */
public class ElementExistenceEvent extends Event
{

//--------------------------------------------------------------------------
//
//  Class constants
//
//--------------------------------------------------------------------------

/**
 *  The <code>ElementExistenceEvent.ELEMENT_ADD</code> constant 
 *  defines the value of the <code>type</code> property of the event 
 *  object for an <code>elementAdd</code> event.
 *
 *  <p>The properties of the event object have the following values:</p>
 *  <table class="innertable">
 *     <tr><th>Property</th><th>Value</th></tr>
 *     <tr><td><code>bubbles</code></td><td>false</td></tr>
 *     <tr><td><code>cancelable</code></td><td>false</td></tr>
 *     <tr><td><code>currentTarget</code></td><td>The Object that defines the 
 *       event listener that handles the event. For example, if you use 
 *       <code>myButton.addEventListener()</code> to register an event listener, 
 *       myButton is the value of the <code>currentTarget</code>. </td></tr>
 *     <tr><td><code>element</code></td><td>Contains a reference
 *         to the visual element that was added.</td></tr>
 *     <tr><td><code>index</code></td><td>The index where the 
 *       visual element that was added.</td></tr>
 *     <tr><td><code>target</code></td><td>The Object that dispatched the event; 
 *       it is not always the Object listening for the event. 
 *       Use the <code>currentTarget</code> property to always access the 
 *       Object listening for the event.</td></tr>
 *  </table>
 *
 *  @eventType elementAdd
 */
public static const ELEMENT_ADD:String = "elementAdd";

/**
 *  The <code>ElementExistenceEvent.ELEMENT_REMOVE</code> constant 
 *  defines the value of the <code>type</code> property of the event 
 *  object for an <code>elementRemove</code> event.
 *
 *  <p>The properties of the event object have the following values:</p>
 *  <table class="innertable">
 *     <tr><th>Property</th><th>Value</th></tr>
 *     <tr><td><code>bubbles</code></td><td>false</td></tr>
 *     <tr><td><code>cancelable</code></td><td>false</td></tr>
 *     <tr><td><code>currentTarget</code></td><td>The Object that defines the 
 *       event listener that handles the event. For example, if you use 
 *       <code>myButton.addEventListener()</code> to register an event listener, 
 *       myButton is the value of the <code>currentTarget</code>. </td></tr>
 *     <tr><td><code>element</code></td><td>Contains a reference
 *        to the visual element that is about to be removed.</td></tr>
 *     <tr><td><code>index</code></td><td>The index where the 
 *       visual element that is being removed.</td></tr>
 *     <tr><td><code>target</code></td><td>The Object that dispatched the event; 
 *       it is not always the Object listening for the event. 
 *       Use the <code>currentTarget</code> property to always access the 
 *       Object listening for the event.</td></tr>
 *  </table>
 *
 *  @eventType elementRemove
 */
public static const ELEMENT_REMOVE:String = "elementRemove";

//--------------------------------------------------------------------------
//
//  Constructor
//
//--------------------------------------------------------------------------

/**
 *  Constructor.
 *
 *  @param type The event type; indicates the action that caused the event.
 *
 *  @param bubbles Specifies whether the event can bubble up the display list hierarchy.
 *
 *  @param cancelable Specifies whether the behavior associated with the event can be prevented.
 *
 *  @param element Reference to the element that was added or removed.
 * 
 *  @param index The index where the element was added or removed.
 */
public function ElementExistenceEvent(
                            type:String, bubbles:Boolean = false,
                            cancelable:Boolean = false,
                            element:IVisualElement = null, 
                            index:int = -1);

//--------------------------------------------------------------------------
//
//  Properties
//
//--------------------------------------------------------------------------

//----------------------------------
//  index
//----------------------------------

/**
 *  The index where the element was added or removed
 */
public var index:int;

//----------------------------------
//  element
//----------------------------------

/**
 *  Reference to the visual element that was added or removed.
 */
public var element:IVisualElement;

}

}

\
\

package mx.components
{
/**
 *  Dispatched when a visual element is added to the content holder.
 *  <code>event.element</code> is the visual element that was added.
 *
 *  @eventType mx.events.ElementExistenceEvent.ELEMENT_ADD
 */
<a href="Event%28name%3D%26quot%3BelementAdd%26quot%3B%2C%20type%3D%26quot%3Bmx.events.ElementExistenceEvent%26quot%3B%29">Event(name="elementAdd", type="mx.events.ElementExistenceEvent")</a>

/**
 *  Dispatched when a visual element is removed to the content holder.
 *  <code>event.element</code> is the visual element that's being removed.
 *
 *  @eventType mx.events.ElementExistenceEvent.ELEMENT_REMOVE
 */
<a href="Event%28name%3D%26quot%3BelementRemove%26quot%3B%2C%20type%3D%26quot%3Bmx.events.ElementExistenceEvent%26quot%3B%29">Event(name="elementRemove", type="mx.events.ElementExistenceEvent")</a>

<a href="DefaultProperty%28%26quot%3BmxmlContent%26quot%3B%29">DefaultProperty("mxmlContent")</a>
class Group extends GroupBase implements IVisualElementContainer
{
//--------------------------------------------------------------------------
//
//  Properties
//
//--------------------------------------------------------------------------

/**
*  @private
*  Content for this Group.  Do not modify this Array directly.
*
*  The content is an array of visual 
*  elements.  If you want to modify the contents of a Group, 
*  use the content API methods instead (addElementAt(), 
*  removeElementAt(), etc...).
*
*  @default null
*
*/
public function get mxmlContent():Array;
public function set mxmlContent(value:Array):void;

//--------------------------------------------------------------------------
//
//  Content management
//
//  The properties and methods in this section mimic the child management
//  APIs available in the flash class DisplayObjectContainer.
//
//--------------------------------------------------------------------------

//----------------------------------
//  Visual Element iteration
//----------------------------------

/**
 *  The number of elements in this group.
 * 
 *  @return The number of visual elements in this group
 */
public function get numElements():int;

/**
 *  Returns the visual element that exists at the specified index.
 *
 *  @param index The index of the element to retrieve.
 *
 *  @return The element at the specified index.
 * 
 *  @throws RangeError If the index position does not exist in the child list.
 */ 
public function getElementAt(index:int):IVisualElement

//----------------------------------
//  Visual Element addition
//----------------------------------

/**
 *  Adds a visual element to this visual container. The element is 
 *  added after all other elements and on top of all other elements.  
 *  (To add a visual element to a specific index position, use 
 *  the <code>addElementAt()</code> method.)
 * 
 *  <p>If you add a visual element object that already has a different
 *  container as a parent, the element is removed from the child 
 *  list of the other container.</p>  
 *
 *  @param element The element to add as a child of this visual container.
 *
 *  @return The element that was added to the visual container.

 * 
 *  @event elementAdded ElementExistenceChangedEvent Dispatched when 
 *  the element is added to the child list.
 * 
 *  @throws ArgumentError If the element is the same as the visual container.
 */   
public function addElement(element:IVisualElement):IVisualElement;

/**
 *  Adds a visual element to this visual container. The element is 
 *  added at the index position specified.  An index of 0 represents   
 *  the first element and the back (bottom) of the display list, unless
 *  <code>layer</code> is specified.
 * 
 *  <p>If you add a visual element object that already has a different
 *  container as a parent, the element is removed from the child 
 *  list of the other container.</p>  
 *
 *  @param element The element to add as a child of this visual container.
 * 
 *  @param index The index position to which the element is added. If 
 *  you specify a currently occupied index position, the child object 
 *  that exists at that position and all higher positions are moved 
 *  up one position in the child list.
 *
 *  @return The element that was added to the visual container.
 * 
 *  @event elementAdded ElementExistenceChangedEvent Dispatched when 
 *  the element is added to the child list.
 * 
 *  @throws ArgumentError If the element is the same as the visual container.
 * 
 *  @throws RangeError If the index position does not exist in the child list.
 */
public function addElementAt(element:IVisualElement, index:int):IVisualElement;

//----------------------------------
//  Visual Element removal
//----------------------------------

/**
 *  Removes the specified visual element from the child list of 
 *  this visual container.  The index positions of any elements 
 *  above the element in this visual container are decreased by 1.
 *
 *  @param element The element to be removed from the visual container.
 *
 *  @return The element removed from the visual container.
 * 
 *  @throws ArgumentError If the element parameter is not a child of 
 *  this visual container.
 */
public function removeElement(element:IVisualElement):IVisualElement;


/**
 *  Removes a visual element from the specified index position 
 *  in the visual container.
 *
 *  @param index The index of the element to remove.
 *
 *  @return The element removed from the visual container.
 * 
 *  @throws RangeError If the index does not exist in the child list.
 */
public function removeElementAt(index:int):IVisualElement;

//----------------------------------
//  Visual Element index
//----------------------------------

/**
 *  Returns the index position of a visual element.
 *
 *  @param element The element to identify.
 *
 *  @return The index position of the element to identify.
 * 
 *  @throws ArgumentError If the element is not a child of this visual container.
 */ 
public function getElementIndex(element:IVisualElement):int;

/**
 *  Changes the position of an existing visual element in the visual container.
 * 
 *  <p>When you call the <code>setElementIndex()</code> method and specify an 
 *  index position that is already occupied, the only positions 
 *  that change are those in between the elements's former and new position.
 *  All others will stay the same.</p>
 *
 *  <p>If a visual element is moved to an index 
 *  lower than its current index, the index of all elements in between increases
 *  by 1.  If an element is moved to an index
 *  higher than its current index, the index of all elements in between 
 *  decreases by 1.</p>
 *
 *  @param element The element for which you want to change the index number.
 * 
 *  @param index The resulting index number for the element.
 * 
 *  @throws RangeError - If the index does not exist in the child list.
 *
 *  @throws ArgumentError - If the element parameter is not a child 
 *  of this visual container.
 */
public function setElementIndex(element:IVisualElement, index:int):void;

//----------------------------------
//  Visual Element swapping
//----------------------------------

/**
*  Swaps the index of the two specified visual elements. All other elements
*  remain in the same index position.
*
*  @param element1 The first visual element.
*  @param element2 The second visual element.
*/
public function swapElements(element1 :IVisualElement, element2 :IVisualElement):void;

/**
 *  Swaps the visual elements at the two specified index 
 *  positions in the visual container.  All other visual 
 *  elements remain in the same index position.
 *
 *  @param index1 The index of the first element.
 * 
 *  @param index2 The index of the second element.
 * 
 *  @throws RangeError If either index does not exist in 
 *  the visual container.
 */
public function swapElementsAt(index1:int, index2:int):void;

//--------------------------------------------------------------------------
//
//  Properties: ScaleGrid
//
//--------------------------------------------------------------------------

public function get scaleGridBottom():Number;
public function set scaleGridBottom(value:Number):void;

public function get scaleGridLeft():Number;
public function set scaleGridLeft(value:Number):void;

public function get scaleGridRight():Number;
public function set scaleGridRight(value:Number):void;

public function get scaleGridTop():Number;
public function set scaleGridTop(value:Number):void;

}
}

B Features


None at this time.

Compiler Work


None required.

Web Tier Compiler Impact


Not Applicable.

Flex Feature Dependencies


States - The new enhanced states syntax supports adding and removing elements from a Group. This work is encapsulated in the AddItems/RemoveItems override tags, and has already been completed.

Layout - There is obviously a tight relationship between layout objects and Group. Refer to the Layout specification for more information.

Focus - Group is not focusable, but elements within the group must be able to receive focus.

Styles - Group does not have styles, but can be a style parent (either direct or proxy) for the elements within the group.

Virtualization - This will most likely be baked into DataGroup.

Scrolling - Containers need the ability to scroll. This will be done through the Scroller class.

Spark DataGroup - Group is for holding visual elements. DataGroup is for holding data items.

Spark SkinnableContainer - The skinnable version of Group.

Backwards Compatibility


Syntax changes

None. This is a new class.

Behavior

None, for existing content. For new content, there are some fundamental differences between Group and the old mx:Container class that need to be understood.

Warnings/Deprecation

None.

Accessibility


None, directly. The Group class itself does not have any accessibility features, but it does need to support accessibility of its children.

Performance


Size and speed are both critical elements of the Group class. A typical application may end up with thousands of Group objects at runtime, so performance testing must be done early and often.

One way we are combatting performance issues, especially for graphical elements is through display object sharing, so multiple graphical elements will share one display object.

Globalization


None. Right to left or vertical orientation is handled by the various layout classes, and does not require any specific functionality from Group.

Localization


Compiler Features

Not applicable.

Framework Features

List the RTE message strings that will require localization. (They must all come from .properties files.)

RTE messages are displayed when calling the Flash DisplayObjectContainer children APIs directly - addChild()/removeChild()/etc.

RTE messages are thrown for argument errors and range errors for the content APIs (addElement, removeElement, etc…).

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.)

None.

List all UI images, skins, sounds, etc. that will require localization.

None.

Discuss any locale-specific formatting requirements for numbers, dates, currency, etc.

None.

Discuss any locale-specific sorting requirements.

None.

Issues and Recommendations


QA



Related

Wiki: Flex 4
Wiki: Gumbo Component Architecture
Wiki: Spark DataGroup
Wiki: Spark Scroller
Wiki: Spark SkinnableContainer
Wiki: Spark Viewport

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.