////////////////////////////////////////////////////////////////////////////////
//
// 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.ISort;
import mx.collections.ISortField;
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;
[DefaultProperty("fields")]
[ResourceBundle("collections")]
[Alternative(replacement="spark.collections.Sort", since="4.5")]
/**
* Provides the sorting information required to establish a sort on an
* existing view (ICollectionView interface or class that implements the
* interface). After you assign a Sort instance to the view's
* sort
property, you must call the view's
* refresh()
method to apply the sort criteria.
*
* Typically the sort is defined for collections of complex items, that is
* collections in which the sort is performed on one or more properties of
* the objects in the collection.
* The following example shows this use:
*
* var col:ICollectionView = new ArrayCollection();
* // In the real world, the collection would have more than one item.
* col.addItem({first:"Anders", last:"Dickerson"});
* // Create the Sort instance.
* var sort:Sort = new Sort();
* // Set the sort field; sort on the last name first, first name second.
* // Both fields are case-insensitive.
* sort.fields = [new SortField("last",true), new SortField("first",true)];
* // Assign the Sort object to the view.
* col.sort = sort;
* // Apply the sort to the collection.
* col.refresh();
*
*
* There are situations in which the collection contains simple items,
* like String
, Date
, Boolean
, etc.
* In this case, apply the sort to the simple type directly.
* When constructing a sort for simple items, use a single sort field,
* and specify a null
name
(first) parameter
* in the SortField object constructor.
* For example:
*
* var col:ICollectionView = new ArrayCollection();
* col.addItem("California");
* col.addItem("Arizona");
* var sort:Sort = new Sort();
* // There is only one sort field, so use a null
* // first parameter.
* sort.fields = [new SortField(null, true)];
* col.sort = sort;
* col.refresh();
*
*
*
* The Flex implementations of the ICollectionView interface retrieve * all items from a remote location before executing a sort. * If you use paging with a sorted list, apply the sort to the remote * collection before you retrieve the data. *
* *By default this Sort 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:Sort>
tag has the following attributes:
* <mx:Sort * Properties * compareFunction="Internal compare function" * fields="null" * unique="false | true" * /> ** *
In case items have inconsistent data types or items have complex data * types, the use of the default built-in compare functions is not recommended. * Inconsistent sorting results may occur in such cases. To avoid such problem, * provide a custom compare function and/or make the item types consistent.
* *Just like any other AdvancedStyleClient
-based classes,
* the Sort
and SortField
classes do not have a
* parent-child relationship in terms of event handling. Locale changes in a
* Sort
instance are not dispatched to its SortField
* instances automatically. The only exceptional case is the internal default
* SortField
instance used when no explicit fields are provided.
* In this case, the internal default SortField
instance follows
* the locale style that the owner Sort
instance has.
Creates a new Sort with no fields set and no custom comparator.
* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function Sort() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * Used for accessing localized Error messages. */ private var resourceManager:IResourceManager = ResourceManager.getInstance(); //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // compareFunction //---------------------------------- /** * @private * Storage for the compareFunction property. */ private var _compareFunction:Function; /** * @private */ private var usingCustomCompareFunction:Boolean; [Inspectable(category="General")] /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get compareFunction():Function { return usingCustomCompareFunction ? _compareFunction : internalCompare; } /** * @private */ public function set compareFunction(value:Function):void { _compareFunction = value; usingCustomCompareFunction = _compareFunction != null; } //---------------------------------- // fields //---------------------------------- /** * @private * Storage for the fields property. */ private var _fields:Array; /** * @private */ private var fieldList:Array = []; [Inspectable(category="General", arrayType="mx.collections.ISortField")] [Bindable("fieldsChanged")] /** * @inheritDoc * * @default null * * @see SortField * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get fields():Array { return _fields; } /** * @private */ public function set fields(value:Array):void { _fields = value; fieldList = []; if (_fields) { var field:ISortField; for (var i:int = 0; i<_fields.length; i++) { field = ISortField(_fields[i]); fieldList.push(field.name); } } dispatchEvent(new Event("fieldsChanged")); } //---------------------------------- // unique //---------------------------------- /** * @private * Storage for the unique property. */ private var _unique:Boolean; [Inspectable(category="General")] /** * @inheritDoc * * @default false * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get unique():Boolean { return _unique; } /** * @inheritDoc */ public function set unique(value:Boolean):void { _unique = value; } //-------------------------------------------------------------------------- // // 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 findItem(items:Array, values:Object, mode:String, returnInsertionIndex:Boolean = false, compareFunction:Function = null):int { var compareForFind:Function; var fieldsForCompare:Array; var message:String; if (!items) { message = resourceManager.getString( "collections", "noItems"); throw new SortError(message); } else if (items.length == 0) { return returnInsertionIndex ? 1 : -1; } if (compareFunction == null) { compareForFind = this.compareFunction; // configure the search criteria if (values && fieldList.length > 0) { fieldsForCompare = []; //build up the fields we can compare, if we skip a field in the //middle throw an error. it is ok to not have all the fields //though var fieldName:String; var hadPreviousFieldName:Boolean = true; for (var i:int = 0; i < fieldList.length; i++) { fieldName = fieldList[i]; if (fieldName) { var hasFieldName:Boolean; try { hasFieldName = values[fieldName] !== undefined; } catch(e:Error) { hasFieldName = false; } if (hasFieldName) { if (!hadPreviousFieldName) { message = resourceManager.getString( "collections", "findCondition", [ fieldName ]); throw new SortError(message); } else { fieldsForCompare.push(fieldName); } } else { hadPreviousFieldName = false; } } else { //this is ok because sometimes a sortfield might //have a custom comparator fieldsForCompare.push(null); } } if (fieldsForCompare.length == 0) { message = resourceManager.getString( "collections", "findRestriction"); throw new SortError(message); } else { try { initSortFields(items[0]); } catch(initSortError:SortError) { //oh well, use the default comparators... } } } } else { compareForFind = compareFunction; } // let's begin searching var found:Boolean = false; var objFound:Boolean = false; var index:int = 0; var lowerBound:int = 0; var upperBound:int = items.length -1; var obj:Object = null; var direction:int = 1; while(!objFound && (lowerBound <= upperBound)) { index = Math.round((lowerBound+ upperBound)/2); obj = items[index]; //if we were given fields for comparison use that method, but //if not the comparator may be for SortField in which case //it'd be an error to pass a 3rd parameter direction = fieldsForCompare ? compareForFind(values, obj, fieldsForCompare) : compareForFind(values, obj); switch(direction) { case -1: upperBound = index -1; break; case 0: objFound = true; switch(mode) { case ANY_INDEX_MODE: found = true; break; case FIRST_INDEX_MODE: found = (index == lowerBound); // start looking towards bof var objIndex:int = index - 1; var match:Boolean = true; while(match && !found && (objIndex >= lowerBound)) { obj = items[objIndex]; var prevCompare:int = fieldsForCompare ? compareForFind(values, obj, fieldsForCompare) : compareForFind(values, obj); match = (prevCompare == 0); if (!match || (match && (objIndex == lowerBound))) { found= true; index = objIndex + (match ? 0 : 1); } // if match objIndex--; } // while break; case LAST_INDEX_MODE: // if we where already at the edge case then we already found the last value found = (index == upperBound); // start looking towards eof objIndex = index + 1; match = true; while(match && !found && (objIndex <= upperBound)) { obj = items[objIndex]; var nextCompare:int = fieldsForCompare ? compareForFind(values, obj, fieldsForCompare) : compareForFind(values, obj); match = (nextCompare == 0); if (!match || (match && (objIndex == upperBound))) { found= true; index = objIndex - (match ? 0 : 1); } // if match objIndex++; } // while break; default: { message = resourceManager.getString( "collections", "unknownMode"); throw new SortError(message); } } // switch break; case 1: lowerBound = index +1; break; } // switch } // while if (!found && !returnInsertionIndex) { return -1; } else { return (direction > 0) ? index + 1 : index; } } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function propertyAffectsSort(property:String):Boolean { if (usingCustomCompareFunction || !fields) return true; for (var i:int = 0; i < fields.length; i++) { var field:ISortField = fields[i]; if (field.name == property || field.usingCustomCompareFunction) { return true; } } return false; } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function reverse():void { if (fields) { for (var i:int = 0; i < fields.length; i++) { ISortField(fields[i]).reverse(); } } noFieldsDescending = !noFieldsDescending; } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function sort(items:Array):void { if (!items || items.length <= 1) { return; } if (usingCustomCompareFunction) { // bug 185872 // the Sort.internalCompare function knows to use Sort._fields; that same logic // needs to be part of calling a custom compareFunction. Of course, a user shouldn't // be doing this -- so I wrap calls to compareFunction with _fields as the last parameter const fixedCompareFunction:Function = function (a:Object, b:Object):int { // append our fields to the call, since items.sort() won't return compareFunction(a, b, _fields); }; var message:String; if (unique) { var uniqueRet1:Object = items.sort(fixedCompareFunction, Array.UNIQUESORT); if (uniqueRet1 == 0) { message = resourceManager.getString( "collections", "nonUnique"); throw new SortError(message); } } else { items.sort(fixedCompareFunction); } } else { var fields:Array = this.fields; if (fields && fields.length > 0) { var i:int; //doing the init value each time may be a little inefficient //but allows for the data to change and the comparators //to update correctly //the sortArgs is an object that if non-null means //we can use Array.sortOn which will be much faster //than going through internalCompare. However //if the Sort is supposed to be unique and fields.length > 1 //we cannot use sortOn since it only tests uniqueness //on the first field var sortArgs:Object = initSortFields(items[0], true); if (unique) { var uniqueRet2:Object; if (sortArgs && fields.length == 1) { uniqueRet2 = items.sortOn(sortArgs.fields[0], sortArgs.options[0] | Array.UNIQUESORT); } else { uniqueRet2 = items.sort(internalCompare, Array.UNIQUESORT); } if (uniqueRet2 == 0) { message = resourceManager.getString( "collections", "nonUnique"); throw new SortError(message); } } else { if (sortArgs) { items.sortOn(sortArgs.fields, sortArgs.options); } else { items.sort(internalCompare); } } } else { items.sort(internalCompare); } } } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * @private * Make sure all SortFields are ready to execute their comparators. */ private function initSortFields(item:Object, buildArraySortArgs:Boolean = false):Object { var arraySortArgs:Object = null; var i:int; for (i = 0; i