Menu

Gumbo DOM Tree API

SourceForge Editorial Staff Sudhir Manjunath
Attachments
Slide1.PNG (39255 bytes)
Slide3.PNG (21839 bytes)
Slide4.PNG (36821 bytes)
Slide5.PNG (73527 bytes)
Slide6.PNG (35044 bytes)
Slide7.PNG (45235 bytes)
There is a newer version of this page. You can find it here.
# Gumbo DOM Tree API - Functional and Design Specification ---- ## Purpose There are multiple DOM trees in Flex 4. How are these exposed? ## Definitions **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. **Component Tree** - The component tree represents the structure of an MXML document. For a simple example, consider a `Panel` which contains a `Label`. In this case, both the `Panel` and the `Label` are in the component tree, but the `Panel's` skin is not. **Layout Tree** - The layout tree represents the runtime layout. In this tree, parents are responsible for displaying and laying out the object, and children are the visual elements being laid out. For a simple example, consider a `Panel` which contains a `Label`. In this case, both the `Panel` and the `Label` are in the layout tree, as is the `Panel's` skin and the `contentGroup` in that skin. **Display Tree** - This is the low-level Flash DisplayObject tree. For Diagrams below, use the following legend: ![](Slide1.PNG) ## Background: When you create an application through MXML markup, a lot happens behind the scenes to translate this into Flash display objects that get displayed. The three main factors contributing to this are skinning, item rendering, and display object sharing. The first two are important concepts to developers; information about the last one is only needed for framework developers but is important to note. ## Skinning: When you instantiate a `Button`, more than one object gets created. For instance: <s:button> Results in a layout tree of: ![](Slide3.PNG) (Note: TextBox has been renamed to Label) There's a Skin file that gets instantiated and put into `Button`'s display list. The skin file for a `Button`, looks like: <s:skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" minwidth="23" minheight="23"> <fx:metadata> HostComponent("mx.components.Button") </fx:metadata> <s:states> <s:state name="up"> <s:state name="over"> <s:state name="down"> <s:state name="disabled"> </s:state></s:state></s:state></s:state></s:states> <s:rect left="0" right="0" top="0" bottom="0" width="70" height="23" radiusx="2" radiusy="2"> <s:stroke> <s:solidcolorstroke color="0x5380D0" color.disabled="0xA9C0E8"> </s:solidcolorstroke></s:stroke> <s:fill> <s:solidcolor color="0xFFFFFF" color.over="0xEBF4FF" color.down="0xDEEBFF"> </s:solidcolor></s:fill> </s:rect> <s:label id="labelDisplay"> </s:label></s:skin> So even though `Button` looks like a leaf node, because of skinning, it actually contains children. To access these elements, use the `skin` property defined on all `SkinnableComponent`s. This gives you the `ButtonSkin` instance, which will let you access the `Rectangle` and `Label`. So to access the `Label`, you would say: `myButton.skin.getElementAt(2)` or `myButton.skin.labelDisplay`. Since `labelDisplay` is an `Button` skin part, you can also use `myButton.labelDisplay`. This same principle applies equally to `SkinnableContainers`. `SkinnableContainers` are containers that innately have children, but they are also `SkinnableComponents` that have a skin and the children from that skin. Taking `Panel` as an example: <s:panel> <s:button> <s:label> <s:checkbox> </s:checkbox></s:label></s:button></s:panel> The panel has three children: a button, a label, and a checkbox. To access them, use the content APIs defined on `SkinnableContainer`. The content APIs mimic the flash `DisplayObjectContainer` APIs, and include `addElement(), addElementAt(), getElementAt(), getElementIndex(), etc…`. The full list of these methods is listed later in the document. Because the panel has 3 children, its component tree looks like: ![](Slide4.PNG) (Note: TextBox has been renamed to Label) However, that's the component tree. Because of skinning, `Panel`'s real layout tree looks like: ![](Slide5.PNG) (Note: TextBox has been renamed to Label) There are a lot of arrows in the diagram above. The important items to note are: * `Panel's` component children are: button, label, and checkbox. * The button, label, and checkbox's component parent (`owner` property) is the Panel. * The button, label, and checkbox's layout parent (`parent` property) is the `contentGroup` of the `PanelSkin`. What this means is that even though it looks like `Panel's` children should be a button, a label, and a checkbox; it's only real child is a panel skin instance. And the button, label, and checkbox get pushed down to become children of the `contentGroup` in the skin file. There are a few ways to access the `Button` in the panel: `myPanel.getElementAt(0)` or `myPanel.contentGroup.getElementAt(0)` or `myPanel.skin.contentGroup.getElementAt(0)`. All `SkinnableComponent's` have a `skin` property. In an `SkinnableContainer`, the children of the components are actually pushed down to the skin's `contentGroup`. The **component tree** refers to the semantic tree translated from MXML. In the `Panel` example, this would just include, the `Panel` and its children: a button, a label, and a checkbox. The **layout tree** refers to the actual tree seen by the layout system, the layout tree due to skinning. In the `Panel` example, this would include the panel, the panel skin, all the panel skin's children, and all the panel's children that are actually pushed down into the `contentGroup` of the panel skin. The layout tree doesn't necessarily correlate to the display list tree that Flash sees. This is because `GraphicElements` are not innately display objects. Because of performance reasons, they implement display object sharing to minimize the number of display objects. `IVisualElementContainer` is the interface that defines the content APIs. In Spark, `Skin`, `Group`, and `SkinnableContainer` are the components for holding visual elements and implement this interface. To provide consistency, MX's `Container` will also implement this interface and just be a facade for `addChild(), numChildren, etc…`. package mx.core { public interface IVisualElementContainer { //---------------------------------- // 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 addElementAt() method.) * *

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.

* * @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 * layer is specified. * *

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.

* * @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. * *

When you call the setElementIndex() 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.

* *

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.

* * @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 swapElementAt(index1:int, index2:int):void; } } This interface makes it easy to descend the tree. Essentially, the interface is a way for containers to advertise what their children are. For example, it will be used by the `FocusManager` for this purpose. This will help so that the focus manager won't be dependent on `Group` or other Spark code (just this interface), and MX applications won't add much code-weight because of it. We debated not adding the mutation APIs here or not having MX implement these APIs, but we think it's more useful for developers (and framework developers) to have all containers (MX and Spark) implement this interface with the mutation APIs. As we look at DataGroup and SkinnableDataContainer, they do not implement `IVisualElementContainer`, though DataGroup has some of the same "read-only" methods, like `numElements` and `getElementAt()`. The `IVisualElementContainer` holds `IVisualElements`. `IVisualElement` is a new interface for visual elements. It will contain a small set of properties and methods needed in order for containers to add the element. It extends `ILayoutElement` and adds a few other properties. //////////////////////////////////////////////////////////////////////////////// // // ADOBE SYSTEMS INCORPORATED // Copyright 2003-2008 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 mx.core { /** * The IVisualElement interface defines the minimum properties and methods * required for a visual element to be laid out and displayed in a Spark container. */ public interface IVisualElement extends ILayoutElement { /** * The owner of this IVisualElement object. * By default, it is the parent of this IVisualElement object. * However, if this IVisualElement object is a child component that is * popped up by its parent, such as the drop-down list of a ComboBox control, * the owner is the component that popped up this IVisualElement object. * *

This property is not managed by Flex, but by each component. * Therefore, if you use the PopUpManger.createPopUp() or * PopUpManger.addPopUp() method to pop up a child component, * you should set the owner property of the child component * to the component that popped it up.

* *

The default value is the value of the parent property.

*/ function get owner():DisplayObjectContainer; function set owner(value:DisplayObjectContainer):void; /** * The parent container or component for this component. * Only visual elements should have a parent property. * Non-visual items should use another property to reference * the object to which they belong. * By convention, non-visual objects use an owner * property to reference the object to which they belong. */ function get parent():DisplayObjectContainer; ...OTHER STUFF NOT DISCUSSED HERE... } } The parent of a visual element is the container directly in charge of laying it out. The owner of a visual element is the component that logically owns it. If a Button is in a `SkinnableContainer`, its parent is the `contentGroup` while its owner is the `SkinnableContainer`. Note that the `parent` and `owner` properties are typed as `DisplayObjectContainer` and not `IVisualElementContainer`. This is because in MX, these properties were typed as `DisplayObjectContainer`. Moreover, because the `parent` property is inherited from Flash's `DisplayObject`, we couldn't change it. We debated coming up with new names for these properties, but in the end, didn't think it was worth it. ### MX Components MX components had the same concepts as above, but a lot of it was hidden behind the scenes. Spark components are more transparent about what's actually going on as skinning is more exposed than before. An MX button has one child, the TextField. This child was added directly to the Button via addChild() (there was no skin). For instance, the TextField for the Button was a direct child of the Button. So if you asked Button for its children, it would give you the TextField. If you asked the TextField for its parent, it would give you the Button. In Spark, a Button has one child, the skin object. The skin object contains a Label. If you ask the Button for its display object children, it would tell you it has one child: the skin. If you want to determine the skinning children of a Button, you should go to the skin object and get the skinning children there. Containers are more difficult and have both the component children and the children in the skin. In MX, a Panel's display list would contain the children due to skinning and a special child called "contentPane." All of the panel's component children would get pushed into the contentPane. This is very similar to the Spark story; however, a lot of this was hidden to the developer in MX. If you asked the Panel for its display list children, it would actually lie to you and give you the contentPane children (Panel's component children). In order to access the skinning children, there was a list of children available through the `rawChildren` property. If you asked a Panel's component child for its parent, it would tell you the panel, even though it's real display list parent was the contentPane. In Spark, `IVisualElementContainer` is an interface that gives you a way to access children. It's also a way for Spark components to advertise who their visual children are. `Group` and `SkinnableContainer` will implement this interface. In addition, MX's `Container` will implement this interface. It'll just facade to the display list APIs, but by doing so, `IVisualElementContainer` will provide a single, consistent way to access a container's children. In Spark, for a `SkinnableContainer`, the DisplayList API is still there. However, if you try to manipulate the DisplayList through this API, we will throw an RTE. If you ask for `numChildren` or `getChildAt()`, unlike in MX, Spark faithfully gives you whatever the display list actually is. If you call the "content API" (numElements, getElementAt()) on the `SkinnableContainer`, it will give you its component children (the contentGroup's actual children). To access the skinning children (the "rawChildren" concept from MX components), you would go down to the skin object. If you asked a Panel's component child who its parent is, it would give you the `contentGroup` (not the Panel, like it does in MX). However, there's another property, owner, that would give you the Panel. The owner property existed in MX as well, but in this scenario in MX, it was the same as the parent property. However, in Spark, owner and parent point to two different objects. ### Data Items In Spark, there are two main types of containers: ones that hold only visual elements and ones that hold data items. `DataGroup` and `SkinnableDataContainer` are made to hold data items. `Group` and `SkinnableContainer` are made to hold visual elements. A data container can hold anything, but it is particularly useful for holding non-visual elements (i.e.-straight up data). The important thing about data containers is that they support item rendering and converting data items into visual elements that can be displayed on screen. #### Item Rendering `DataGroup` has the ability to take arbitrary, non-visual elements and render them onscreen. Because of this, item renderers are inserted into the layout tree as needed. In some cases, even visual elements, such as `UIComponents` or `GraphicElements`, are wrapped in item renderers as well. To expose this model to developers, we considered several options: 1. DataGroup and SkinnableDataContainer are considered leaf nodes and their actual visual children aren't accessible 2. DataGroup and SkinnableDataContainer implement `IVisualElementContainer`. When asked for the number of visual elements on screen, we return only the elements that are currently being renderred. Mutation APIs RTE. 3. DataGroup and SkinnableDataContainer implement `IVisualElementContainer`. When asked for the number of visual elements on screen, we return the total list of items. If the user asks for a visual element of something that isn't currently being renderred, we will create an item renderer for them. We decided to add the "read-only" element APIs to `DataGroup`, like `numElements`, `getElementAt()`, and `getElementIndex()`. There is also another API, `getItemIndicesInView()` to determine which data items are in view. As in MX, an item renderer's `owner` property always refers to the same object as the owner property of the component it's wrapping. An item renderer's `parent` property is the parent in charge of laying it out. Here are some diagrams showing item rendering in action. ![](Slide6.PNG) You'll notice a `DataGroup` has no children in the component tree. This is because it's considered a leaf node--one that renders data. Here's a look at the layout tree: ![](Slide7.PNG) (Note: TextBox has been renamed to Label) In the example above, the String is not a visual element and needs an item renderer. An item renderer comes in and wraps the String object. Its `owner` property is the `DataGroup`. Because an itemRendererFunction is set, the same thing happens for Employee Object and the other String. ## Use Cases: Developers typically interact with the component tree exclusively. Layouts and effects interact with the layout tree, as does FocusManager. Only low-level code like Group's DisplayObject sharing code will interact with the display tree. ## API Description public interface IVisualElementContainer { public function get numElements():int; public function getElementAt(index:int):IVisualElement; public function getElementIndex(element:IVisualElement):int; public function addElement(element:IVisualElement):IVisualElement; public function addElementAt(element:IVisualElement, index:int):IVisualElement; public function removeElement(element:IVisualElement):IVisualElement; public function removeElementAt(index:int):IVisualElement; public function setElementIndex(element:IVisualElement, index:int):void; public function swapElements(element1:IVisualElement, element2:IVisualElement):void; public function swapElementsAt(index1:int, index2:int):void; } public interface IVisualElement extends ILayoutElement { owner:DisplayObjectContainer; parent:DisplayObjectContainer; ...other stuff not discussed here... } public class UIComponent implements IVisualElement { owner:DisplayObjectContainer; parent:DisplayObjectContainer; ...other stuff... } public class GraphicElement implements IVisualElement { owner:DisplayObjectContainer; parent:DisplayObjectContainer; ...other stuff... } DefaultProperty("content") public class Group extends GroupBase implements IVisualElementContainer { write-only mxmlContent:Array; layout:ILayout; public function get numElements():int; public function getElementAt(index:int):IVisualElement; public function getElementIndex(element:IVisualElement):int; public function addElement(element:IVisualElement):IVisualElement; public function addElementAt(element:IVisualElement, index:int):IVisualElement; public function removeElement(element:IVisualElement):IVisualElement; public function removeElementAt(index:int):IVisualElement; public function setElementIndex(element:IVisualElement, index:int):void; public function swapElements(element1:IVisualElement, element2:IVisualElement):void; public function swapElementsAt(index1:int, index2:int):void; } public class Skin extends Group { } public class SkinnableComponent extends UIComponent { function get skin():Skin; CSS function set skinClass:Class; } DefaultProperty("content") public class SkinnableContainer extends SkinnableContainerBase implements IVisualElementContainer { write-only mxmlContent:Array; public function get numElements():int; public function getElementAt(index:int):IVisualElement; public function getElementIndex(element:IVisualElement):int; public function addElement(element:IVisualElement):IVisualElement; public function addElementAt(element:IVisualElement, index:int):IVisualElement; public function removeElement(element:IVisualElement):IVisualElement; public function removeElementAt(index:int):IVisualElement; public function setElementIndex(element:IVisualElement, index:int):void; public function swapElements(element1:IVisualElement, element2:IVisualElement):void; public function swapElementsAt(index1:int, index2:int):void; SkinPart contentGroup:Group; } public class Container extends UIComponent implements IVisualElementContainer { public function get numElements():int; public function getElementAt(index:int):IVisualElement; public function getElementIndex(element:IVisualElement):int; public function addElement(element:IVisualElement):IVisualElement; public function addElementAt(element:IVisualElement, index:int):IVisualElement; public function removeElement(element:IVisualElement):IVisualElement; public function removeElementAt(index:int):IVisualElement; public function setElementIndex(element:IVisualElement, index:int):void; public function swapElements(element1:IVisualElement, element2:IVisualElement):void; public function swapElementsAt(index1:int, index2:int):void; } DefaultProperty("dataProvider") public class DataGroup extends UIComponent { dataProvider:IList; itemRenderer/itemRendererFunction; layout:ILayout; public function get numElements():int; public function getElementAt(index:int):IVisualElement; public function getElementIndex(element:IVisualElement):int; public function getItemIndicesInView():Vector.<int>; } DefaultProperty("dataProvider") public class SkinnableDataContainer extends SkinnableContainerBase { dataProvider:IList; layout:ILayout; itemRenderer/itemRendererFunction; SkinPart dataGroup:DataGroup; } Some sample code for walking the trees: public function walkTree(element:IVisualElement, proc:Function):void { proc(element); if (element is IVisualElementContainer) { var visualContainer:IVisualElementContainer = IVisualElementContainer(element); for (var i:int = 0; i < visualContainer.numElements; i++) { walkTree(visualContainer.getElementAt(i)); } } } public function walkLayoutTree(element:IVisualElement, proc:Function):void { proc(element); if (element is SkinnableComponent) { var skin:Skin = SkinnableComponent(element).skin; walkTree(skin); } else if (element is IVisualElementContainer) { var visualContainer:IVisualElementContainer = IVisualElementContainer(element); for (var i:int = 0; i < visualContainer.numElements; i++) { walkTree(visualContainer.getElementAt(i)); } } // expand this to MX and IRawChildrenContainer? } public function walkUpTree(element:IVisualElement, proc:Function):void { while (element!= null) { proc(element); element = element .owner; } } public function walkUpLayoutTree(element :IVisualElement, proc:Function):void { while (element != null) { proc(element ); element = element .parent; } } ## More on parent/owner One way to think about the `parent` property is "who is laying you out." This also corresponds with your physical display list parent if you're a DisplayObject. (GraphicElements do a little faking here since they aren't display objects but it's the same concept). The `owner` property has a few purposes: * It can tell you who your parent in the component tree is (elements in an `SkinnableContainer`) * It can tell item renderers, what data container is in charge of them * It can be used for popups, like `DateField`, to let you know who's in charge of the popup. The way to think about the `owner` property is that an element's `owner` refers to the component that's in charge of it. ## Changes that need to be made The changes that need to be made are adding in a `parent` property and an `owner` property to `GraphicElement`. We'd also need to add code in the proper places to hook up to these properties (item renderers and SkinnableContainer) The other main part of this proposal is creating and implementing the interfaces. We have the `Group/DataGroup` split. This is work that is spec-ed separately and completed as a separate (though related) work item. Lastly, there's work that needs to be done with the virtualization spec around how to expose the renderred visual elements. ## Important/Contentious Points: * Having all these different trees is confusing, but it's something we should explain clearly to Flex developers. * The reason why we're introducing the `owner` property is so that people can walk up the logical DOM. The reason why we're introducing the `parent` property is so people can walk up the layout DOM. We debated not using the `parent` property because it's typed as `DisplayObjectContainer`, and at some point in the future, parent nodes may not necessarily be `DisplayObjectContainers`. However, for now they are, and `parent` just makes a lot more sense than any other name. If we do decide to make containers non-DisplayObjects, then we might as well go down this path for everything and make everything DisplayObject optional. * Walking the layout tree requires knowledge of `SkinnableComponent` and `Skin`. This means Mustella (or other places) will need to bring these classes in (or treat them as untyped). * MX containers also implement `IVisualElementContainer`. * `owner` seems to serve 3 separate purposes. * `Scroller` also implements `IVisualElementContainer` to advertise the one child it contains. We debated having a separate interface for "decorators", but our approach seems more generic and can handle `HDividedBoxes` well. The "getter" APIs will work fine on `Scroller`, while the mutating ones will throw RTEs. * We debated supporting raw flash display objects in gumbo containers, but decided against it. * We need to support a new Flash Component Kit and older swcs made from the old Flash Component Kit. One solution is to always link in UIMovieClip and the other FCK classes. These new definitions will implement `IVisualElement` and `IVisualElementContainer`. Because these classes are newer definitions, they will overwrite the old versions of the base classes. Another solution is to just update the Flash Component Kit and don't support the older swcs. We'll need more direction from product management; however, regardless, these classes need to be updated. * We have a parallel set of Flash DisplayObjectContainer APIs that are inherited by Group/DataGroup/SkinnableComponent (addChild(), getChildAt(), etc.). In order to deal with this, all calls to mutation APIs (addChild(), removeChild(), swapChildren(), etc…) will throw an RTE. Calls to the "getters" will be allowed. We tried putting an RTE in the non-mutating APIs (getChildAt, numChildren, etc…), but fundamental issues arose, like UIComponent's removeChildAt being dependent on it. If this turns out to be a priority, we can add RTEs for these methods and provide new methods, like $getChildAt_SkinnableComponent, to access these methods. Then we can change all framework code dependent on these APIs. There's another write-up around this and other issues that come from this: [Child APIs vs. Item APIs](Child%20APIs%20vs.%20Item%20APIs).
[[ include ref='flexsdk_rightnav' ]]
oinclude:site:open_commentlogin}
</int></s:button>

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.