blob: 89618e0e0baa5d87e82894646b0257047551c26e [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.controls.treeClasses
{
import flash.utils.Dictionary;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.XMLListCollection;
import mx.controls.menuClasses.IMenuDataDescriptor;
import mx.core.mx_internal;
import mx.utils.UIDUtil;
use namespace mx_internal;
/**
* The DefaultDataDescriptor class provides a default implementation for
* accessing and manipulating data for use in controls such as Tree and Menu.
*
* This implementation handles e4x XML and object nodes in similar but different
* ways. See each method description for details on how the method
* accesses values in nodes of various types.
*
* This class is the default value of the Tree, Menu, MenuBar, and
* PopUpMenuButton control <code>dataDescriptor</code> properties.
*
* @see mx.controls.treeClasses.ITreeDataDescriptor
* @see mx.controls.menuClasses.IMenuDataDescriptor
* @see mx.controls.Menu
* @see mx.controls.MenuBar
* @see mx.controls.PopUpMenuButton
* @see mx.controls.Tree
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class DefaultDataDescriptor implements ITreeDataDescriptor2, IMenuDataDescriptor
{
include "../../core/Version.as";
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function DefaultDataDescriptor()
{
super();
}
/**
* @private
*/
private var ChildCollectionCache:Dictionary = new Dictionary(true);
/**
* Provides access to a node's children. Returns a collection
* of children if they exist. If the node is an Object, the method
* returns the contents of the object's <code>children</code> field as
* an ArrayCollection.
* If the node is XML, the method returns an XMLListCollection containing
* the child elements.
*
* @param node The node object currently being evaluated.
* @param model The collection that contains the node; ignored by this class.
* @return An object containing the children nodes.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getChildren(node:Object, model:Object = null):ICollectionView
{
if (node == null)
return null;
var children:*;
var childrenCollection:ICollectionView;
//first get the children based on the type of node.
if (node is XML)
{
//trace("getChildren", node.toXMLString());
children = node.*;
}
else if (node is Object)
{
//we'll try the default children property
try
{
children = node.children;
}
catch(e:Error)
{
}
}
//no children exist for this node
if (children == undefined && !(children is XMLList))
return null;
//then wrap children in ICollectionView if necessary
if (children is ICollectionView)
{
childrenCollection = ICollectionView(children);
}
else if (children is Array)
{
var oldArrayCollection:ArrayCollection = ChildCollectionCache[node];
if (!oldArrayCollection)
{
childrenCollection = new ArrayCollection(children);
ChildCollectionCache[node] = childrenCollection;
}
else
{
childrenCollection = oldArrayCollection;
ArrayCollection(childrenCollection).dispatchResetEvent = false;
ArrayCollection(childrenCollection).source = children;
}
}
else if (children is XMLList)
{
var oldXMLCollection:XMLListCollection = ChildCollectionCache[node];
if (!oldXMLCollection)
{
// double check since XML as dictionary keys is inconsistent
for (var p:* in ChildCollectionCache)
{
if (p === node)
{
oldXMLCollection = ChildCollectionCache[p];
break;
}
}
}
if (!oldXMLCollection)
{
childrenCollection = new XMLListCollection(children);
ChildCollectionCache[node] = childrenCollection;
}
else
{
childrenCollection = oldXMLCollection;
//We don't want to send a RESET type of collectionChange event in this case.
XMLListCollection(childrenCollection).dispatchResetEvent = false;
XMLListCollection(childrenCollection).source = children;
}
}
else
{
var childArray:Array = new Array(children);
if (childArray != null)
{
childrenCollection = new ArrayCollection(childArray);
}
}
return childrenCollection;
}
/**
* Determines if the node actually has children.
*
* @param node The node object currently being evaluated.
* @param model The collection that contains the node; ignored by this class.
*
* @return <code>true</code> if this node currently has children.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function hasChildren(node:Object, model:Object = null):Boolean
{
if (node == null)
return false;
//This default impl can't optimize this call to getChildren
//since we can't make any assumptions by type. Custom impl's
//can probably avoid this call and reduce the number of calls to
//getChildren if need be.
var children:ICollectionView = getChildren(node, model);
try
{
if (children.length > 0)
return true;
}
catch(e:Error)
{
}
return false;
}
/**
* Tests a node for termination.
* Branches are non-terminating but are not required to have any leaf nodes.
* If the node is XML, returns <code>true</code> if the node has children
* or a <code>true isBranch</code> attribute.
* If the node is an object, returns <code>true</code> if the node has a
* (possibly empty) <code>children</code> field.
*
* @param node The node object currently being evaluated.
* @param model The collection that contains the node; ignored by this class.
*
* @return <code>true</code> if this node is non-terminating.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isBranch(node:Object, model:Object = null):Boolean
{
if (node == null)
return false;
var branch:Boolean = false;
if (node is XML)
{
var childList:XMLList = node.children();
//accessing non-required e4x attributes is quirky
//but we know we'll at least get an XMLList
var branchFlag:XMLList = node.@isBranch;
//check to see if a flag has been set
if (branchFlag.length() == 1)
{
//check flag and return (this flag overrides termination status)
if (branchFlag[0] == "true")
branch = true;
}
//since no flags, we'll check to see if there are children
else if (childList.length() != 0)
{
branch = true;
}
}
else if (node is Object)
{
try
{
if (node.children != undefined)
{
branch = true;
}
}
catch(e:Error)
{
}
}
return branch;
}
/**
* Returns a node's data.
* Currently returns the entire node.
*
* @param node The node object currently being evaluated.
* @param model The collection that contains the node; ignored by this class.
* @return The node.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getData(node:Object, model:Object = null):Object
{
return Object(node);
}
/**
* Add a child node to a node at the specified index.
* This implementation does the following:
*
* <ul>
* <li>If the <code>parent</code> parameter is null or undefined,
* inserts the <code>child</code> parameter at the
* specified index in the collection specified by <code>model</code>
* parameter.
* </li>
* <li>If the <code>parent</code> parameter has a <code>children</code>
* field or property, the method adds the <code>child</code> parameter
* to it at the <code>index</code> parameter location.
* In this case, the <code>model</code> parameter is not required.
* </li>
* <li>If the <code>parent</code> parameter does not have a <code>children</code>
* field or property, the method adds the <code>children</code>
* property to the <code>parent</code>. The method then adds the
* <code>child</code> parameter to the parent at the
* <code>index</code> parameter location.
* In this case, the <code>model</code> parameter is not required.
* </li>
* <li>If the <code>index</code> value is greater than the collection
* length or number of children in the parent, adds the object as
* the last child.
* </li>
* </ul>
*
* @param parent The node object that will parent the child.
* @param newChild The node object that will be parented by the node.
* @param index The 0-based index of where to put the child node relative to the parent.
* @param model The entire collection that this node is a part of.
*
* @return <code>true</code> if successful.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addChildAt(parent:Object, newChild:Object, index:int, model:Object = null):Boolean
{
if (!parent)
{
try
{
if (index > model.length)
index = model.length;
if (model is IList)
IList(model).addItemAt(newChild, index);
else
{
var cursor:IViewCursor = model.createCursor();
cursor.seek(CursorBookmark.FIRST, index);
cursor.insert(newChild);
}
return true;
}
catch(e:Error)
{
}
}
else
{
var children:ICollectionView = ICollectionView(getChildren(parent, model));
if (!children)
{
if (parent is XML)
{
var temp:XMLList = new XMLList();
XML(parent).appendChild(temp);
children = new XMLListCollection(parent.children());
}
else if (parent is Object)
{
parent.children = new ArrayCollection();
children = parent.children;
}
}
try
{
if (index > children.length)
index = children.length;
if (children is IList)
IList(children).addItemAt(newChild, index);
else
{
cursor = children.createCursor();
cursor.seek(CursorBookmark.FIRST, index);
cursor.insert(newChild);
}
return true;
}
catch(e:Error)
{
}
}
return false;
}
/**
* Removes the child node from a node at the specified index.
* If the <code>parent</code> parameter is null
* or undefined, the method uses the <code>model</code> parameter to
* access the child; otherwise, it uses the <code>parent</code> parameter
* and ignores the <code>model</code> parameter.
*
* @param parent The node object that currently parents the child node.
* @param child The node that is being removed.
* @param index The 0-based index of the child node to remove relative to the parent.
* @param model The entire collection that this node is a part of.
*
* @return <code>true</code> if successful.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeChildAt(parent:Object, child:Object, index:int, model:Object = null):Boolean
{
//handle top level where there is no parent
if (!parent)
{
try
{
if (index > model.length)
index = model.length;
if (model is IList)
model.removeItemAt(index);
else
{
var cursor:IViewCursor = model.createCursor();
cursor.seek(CursorBookmark.FIRST, index);
cursor.remove();
}
return true;
}
catch(e:Error)
{
}
}
else
{
var children:ICollectionView = ICollectionView(getChildren(parent, model));
try
{
if (index > children.length)
index = children.length;
if (children is IList)
IList(children).removeItemAt(index);
else
{
cursor = children.createCursor();
cursor.seek(CursorBookmark.FIRST, index);
cursor.remove();
}
return true;
}
catch(e:Error)
{
}
}
return false;
}
/**
* Returns the type identifier of a node.
* This method is used by menu-based controls to determine if the
* node represents a separator, radio button,
* a check box, or normal item.
*
* @param node The node object for which to get the type.
*
* @return The value of the <code>type</code> attribute or field,
* or the empty string if there is no such field.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getType(node:Object):String
{
if (node is XML)
{
return String(node.@type);
}
else if (node is Object)
{
try
{
return String(node.type);
}
catch(e:Error)
{
}
}
return "";
}
/**
* Returns whether the node is enabled.
* This method is used by menu-based controls.
*
* @param node The node for which to get the status.
*
* @return The value of the node's <code>enabled</code>
* attribute or field, or <code>true</code> if there is no such
* entry or the value is not <code>false</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isEnabled(node:Object):Boolean
{
var enabled:*;
if (node is XML)
{
enabled = node.@enabled;
if (enabled[0] == false)
return false;
}
else if (node is Object)
{
try
{
return !("false" == String(node.enabled))
}
catch(e:Error)
{
}
}
return true;
}
/**
* Sets the value of the field or attribute in the data provider
* that identifies whether the node is enabled.
* This method sets the value of the node's <code>enabled</code>
* attribute or field.
* This method is used by menu-based controls.
*
* @param node The node for which to set the status.
* @param value Whether the node is enabled.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setEnabled(node:Object, value:Boolean):void
{
if (node is XML)
{
node.@enabled = value;
}
else if (node is Object)
{
try
{
node.enabled = value;
}
catch(e:Error)
{
}
}
}
/**
* Returns whether the node is toggled.
* This method is used by menu-based controls.
*
* @param node The node for which to get the status.
*
* @return The value of the node's <code>toggled</code>
* attribute or field, or <code>false</code> if there is no such
* entry.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isToggled(node:Object):Boolean
{
if (node is XML)
{
var toggled:* = node.@toggled;
if (toggled[0] == true)
return true;
}
else if (node is Object)
{
try
{
return Boolean(node.toggled);
}
catch(e:Error)
{
}
}
return false;
}
/**
* Sets the value of the field or attribute in the data provider
* that identifies whether the node is toggled.
* This method sets the value of the node's <code>toggled</code>
* attribute or field.
* This method is used by menu-based controls.
*
* @param node The node for which to set the status.
* @param value Whether the node is toggled.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setToggled(node:Object, value:Boolean):void
{
if (node is XML)
{
node.@toggled = value;
}
else if (node is Object)
{
try
{
node.toggled = value;
}
catch(e:Error)
{
}
}
}
/**
* Returns the name of the radio button group to which
* the node belongs, if any.
* This method is used by menu-based controls.
*
* @param node The node for which to get the group name.
* @return The value of the node's <code>groupName</code>
* attribute or field, or an empty string if there is no such
* entry.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getGroupName(node:Object):String
{
if (node is XML)
{
return node.@groupName;
}
else if (node is Object)
{
try
{
return node.groupName;
}
catch(e:Error)
{
}
}
return "";
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getHierarchicalCollectionAdaptor(hierarchicalData:ICollectionView,
uidFunction:Function,
openItems:Object,
model:Object = null):ICollectionView
{
return new HierarchicalCollectionView(hierarchicalData,
this,
uidFunction,
openItems);
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getNodeDepth(node:Object, iterator:IViewCursor, model:Object = null):int
{
if (node == iterator.current)
return HierarchicalViewCursor(iterator).currentDepth;
return -1;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getParent(node:Object, collection:ICollectionView, model:Object = null):Object
{
return HierarchicalCollectionView(collection).getParentItem(node);
}
}
}