//////////////////////////////////////////////////////////////////////////////// // // 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.collections { import flash.events.Event; import flash.events.EventDispatcher; import mx.collections.errors.SortError; import mx.managers.ISystemManager; import mx.managers.SystemManager; import mx.resources.IResourceManager; import mx.resources.ResourceManager; import mx.utils.ObjectUtil; [ResourceBundle("collections")] [Alternative(replacement="spark.collections.SortField", since="4.5")] /** * Provides the sorting information required to establish a sort on a field * or property in a collection view. * * The SortField class is meant to be used with the Sort class. * * Typically the sort is defined for collections of complex items, that is * items in which the sort is performed on properties of those objects. * As in the following example: * *
* var col:ICollectionView = new ArrayCollection();
* col.addItem({first:"Anders", last:"Dickerson"});
* var sort:Sort = new Sort();
* sort.fields = [new SortField("first", true)];
* col.sort = sort;
*
*
* There are situations in which the collection contains simple items, like
* String
, Date
, Boolean
, etc.
* In this case, sorting should be applied to the simple type directly.
* When constructing a sort for this situation only a single sort field is
* required and should not have a name
specified.
* For example:
*
*
* var col:ICollectionView = new ArrayCollection();
* col.addItem("California");
* col.addItem("Arizona");
* var sort:Sort = new Sort();
* sort.fields = [new SortField(null, true)];
* col.sort = sort;
*
*
* By default the comparison provided by the SortField class does
* not provide correct language specific
* sorting for strings. For this type of sorting please see the
* spark.collections.Sort
and
* spark.collections.SortField
classes.
The <mx:SortField>
tag has the following attributes:
* <mx:SortField * Properties * caseInsensitive="false" * compareFunction="Internal compare function" * descending="false" * name="null" * numeric="null" * /> ** * @see mx.collections.ICollectionView * @see mx.collections.Sort * @see spark.collections.Sort * @see spark.collections.SortField * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class SortField extends EventDispatcher implements ISortField { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @param name The name of the property that this field uses for * comparison. * If the object is a simple type, pass
null
.
* @param caseInsensitive When sorting strings, tells the comparator
* whether to ignore the case of the values.
* @param descending Tells the comparator whether to arrange items in
* descending order.
* @param numeric Tells the comparator whether to compare sort items as
* numbers, instead of alphabetically.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function SortField(name:String = null,
caseInsensitive:Boolean = false,
descending:Boolean = false,
numeric:Object = null)
{
super();
_name = name;
_caseInsensitive = caseInsensitive;
_descending = descending;
_numeric = numeric;
_compareFunction = stringCompare;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Used for accessing localized Error messages.
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//---------------------------------
// arraySortOnOptions
//---------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get arraySortOnOptions():int
{
if (usingCustomCompareFunction
|| name == null
|| _compareFunction == xmlCompare
|| _compareFunction == dateCompare)
{
return -1;
}
var options:int = 0;
if (caseInsensitive) options |= Array.CASEINSENSITIVE;
if (descending) options |= Array.DESCENDING;
if (numeric == true || _compareFunction == numericCompare) options |= Array.NUMERIC;
return options;
}
//---------------------------------
// caseInsensitive
//---------------------------------
/**
* @private
* Storage for the caseInsensitive property.
*/
private var _caseInsensitive:Boolean;
[Inspectable(category="General")]
[Bindable("caseInsensitiveChanged")]
/**
* Specifies whether the sort for this field should be case insensitive.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get caseInsensitive():Boolean
{
return _caseInsensitive;
}
/**
* @private
*/
public function set caseInsensitive(value:Boolean):void
{
if (value != _caseInsensitive)
{
_caseInsensitive = value;
dispatchEvent(new Event("caseInsensitiveChanged"));
}
}
//---------------------------------
// compareFunction
//---------------------------------
/**
* @private
* Storage for the compareFunction property.
*/
private var _compareFunction:Function;
[Inspectable(category="General")]
/**
* The function that compares two items during a sort of items for the
* associated collection. If you specify a compareFunction
* property in an ISort object, Flex ignores any
* compareFunction
properties of the ISort's SortField
* objects.
* The compare function must have the following signature:
* *function myCompare(a:Object, b:Object):int
This function must return the following values:
* *a
should appear before b
in
* the sorted sequencea
equals b
a
should appear after b
in the
* sorted sequenceThe default value is an internal compare function that can perform * a string, numeric, or date comparison in ascending or descending order, * with case-sensitive or case-insensitive string comparisons. * Specify your own function only if you need a need a custom comparison * algorithm. This is normally only the case if a calculated field is * used in a display.
* * Note if you need, language specific sorting then consider using the *spark.collections.SortField
class.
*
* @see spark.collections.SortField
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get compareFunction():Function
{
return _compareFunction;
}
/**
* @private
*/
public function set compareFunction(c:Function):void
{
_compareFunction = c;
_usingCustomCompareFunction = (c != null);
}
//---------------------------------
// descending
//---------------------------------
/**
* @private
* Storage for the descending property.
*/
private var _descending:Boolean;
[Inspectable(category="General")]
[Bindable("descendingChanged")]
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get descending():Boolean
{
return _descending;
}
/**
* @private
*/
public function set descending(value:Boolean):void
{
if (_descending != value)
{
_descending = value;
dispatchEvent(new Event("descendingChanged"));
}
}
//---------------------------------
// name
//---------------------------------
/**
* @private
* Storage for the name property.
*/
private var _name:String;
[Inspectable(category="General")]
[Bindable("nameChanged")]
/**
* @inheritDoc
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get name():String
{
return _name;
}
/**
* @private
*/
public function set name(n:String):void
{
_name = n;
dispatchEvent(new Event("nameChanged"));
}
//---------------------------------
// numeric
//---------------------------------
/**
* @private
* Storage for the numeric property.
*/
private var _numeric:Object;
[Inspectable(category="General")]
[Bindable("numericChanged")]
/**
* @inheritDoc
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get numeric():Object
{
return _numeric;
}
/**
* @private
*/
public function set numeric(value:Object):void
{
if (_numeric != value)
{
_numeric = value;
dispatchEvent(new Event("numericChanged"));
}
}
//---------------------------------
// usingCustomCompareFunction
//---------------------------------
private var _usingCustomCompareFunction:Boolean;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get usingCustomCompareFunction():Boolean
{
return _usingCustomCompareFunction;
}
//--------------------------------------------------------------------------
//
// Overridden Methods
//
//--------------------------------------------------------------------------
/**
* @private
* A pretty printer for Sort that lists the sort fields and their
* options.
*/
override public function toString():String
{
return ObjectUtil.toString(this);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function initializeDefaultCompareFunction(obj:Object):void
{
// if the compare function is not already set then we can set it
if (!usingCustomCompareFunction)
{
if (numeric == true)
_compareFunction = numericCompare;
else if (caseInsensitive || numeric == false)
_compareFunction = stringCompare;
else
{
// we need to introspect the data a little bit
var value:Object;
if (_name)
{
try
{
value = obj[_name];
}
catch(error:Error)
{
}
}
//this needs to be an == null check because !value will return true
//where value == 0 or value == false
if (value == null)
{
value = obj;
}
var typ:String = typeof(value);
switch (typ)
{
case "string":
_compareFunction = stringCompare;
break;
case "object":
if (value is Date)
{
_compareFunction = dateCompare;
}
else
{
_compareFunction = stringCompare;
var test:String;
try
{
test = value.toString();
}
catch(error2:Error)
{
}
if (!test || test == "[object Object]")
{
_compareFunction = nullCompare;
}
}
break;
case "xml":
_compareFunction = xmlCompare;
break;
case "boolean":
case "number":
_compareFunction = numericCompare;
break;
}
} // else
} // if
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function reverse():void
{
descending = !descending;
}
//--------------------------------------------------------------------------
//
// Private Methods
//
//--------------------------------------------------------------------------
private function nullCompare(a:Object, b:Object):int
{
var value:Object;
var left:Object;
var right:Object;
var found:Boolean = false;
// return 0 (ie equal) if both are null
if (a == null && b == null)
{
return 0;
}
// we need to introspect the data a little bit
if (_name)
{
try
{
left = a[_name];
}
catch(error:Error)
{
}
try
{
right = b[_name];
}
catch(error:Error)
{
}
}
// return 0 (ie equal) if both are null
if (left == null && right == null)
return 0;
if (left == null && !_name)
left = a;
if (right == null && !_name)
right = b;
var typeLeft:String = typeof(left);
var typeRight:String = typeof(right);
if (typeLeft == "string" || typeRight == "string")
{
found = true;
_compareFunction = stringCompare;
}
else if (typeLeft == "object" || typeRight == "object")
{
if (left is Date || right is Date)
{
found = true;
_compareFunction = dateCompare
}
}
else if (typeLeft == "xml" || typeRight == "xml")
{
found = true;
_compareFunction = xmlCompare;
}
else if (typeLeft == "number" || typeRight == "number"
|| typeLeft == "boolean" || typeRight == "boolean")
{
found = true;
_compareFunction = numericCompare;
}
if (found)
{
return _compareFunction(left, right);
}
else
{
var message:String = resourceManager.getString(
"collections", "noComparatorSortField", [ name ]);
throw new SortError(message);
}
}
/**
* Pull the numbers from the objects and call the implementation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function numericCompare(a:Object, b:Object):int
{
var fa:Number;
try
{
fa = _name == null ? Number(a) : Number(a[_name]);
}
catch(error:Error)
{
}
var fb:Number;
try
{
fb = _name == null ? Number(b) : Number(b[_name]);
}
catch(error:Error)
{
}
return ObjectUtil.numericCompare(fa, fb);
}
/**
* Pull the date objects from the values and compare them.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function dateCompare(a:Object, b:Object):int
{
var fa:Date;
try
{
fa = _name == null ? a as Date : a[_name] as Date;
}
catch(error:Error)
{
}
var fb:Date;
try
{
fb = _name == null ? b as Date : b[_name] as Date;
}
catch(error:Error)
{
}
return ObjectUtil.dateCompare(fa, fb);
}
/**
* Pull the strings from the objects and call the implementation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function stringCompare(a:Object, b:Object):int
{
var fa:String;
try
{
fa = _name == null ? String(a) : String(a[_name]);
}
catch(error:Error)
{
}
var fb:String;
try
{
fb = _name == null ? String(b) : String(b[_name]);
}
catch(error:Error)
{
}
return ObjectUtil.stringCompare(fa, fb, _caseInsensitive);
}
/**
* Pull the values out fo the XML object, then compare
* using the string or numeric comparator depending
* on the numeric flag.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function xmlCompare(a:Object, b:Object):int
{
var sa:String;
try
{
sa = _name == null ? a.toString() : a[_name].toString();
}
catch(error:Error)
{
}
var sb:String;
try
{
sb = _name == null ? b.toString() : b[_name].toString();
}
catch(error:Error)
{
}
if (numeric == true)
{
return ObjectUtil.numericCompare(parseFloat(sa), parseFloat(sb));
}
else
{
return ObjectUtil.stringCompare(sa, sb, _caseInsensitive);
}
}
}
}