Flex 4.6 Custom Sorting of list - apache-flex

So my problem seems to be simple but for the love of god i cant figure it out. So i am asking for your help. I have a simple list in a mobile application containing shops. I want to short them by distance from the center of my map.
It seems like i need a custom sorting function but im not sure on whati have to do in it.
i am using
testDist.setLatLng(propertyList.selectedItem.lat,propertyList.selectedItem.lng);
dist.text=""+GeodesicCalculatorUtil.calculateGeodesicDistance(FlexGlobals.topLevelApplication.currentLatLng2,testDist,DistanceUnits.KILOMETERS)
to get the distance for a shop and i have to compare it with the next one. however i cant figure out how to do it in the comparing function. I would be glad if anyone can help me.

As this seems to be a common problem for people using MapQuest as their mapping system, i provide my solution for sorting the custom POIs by distance to any list. This is a solution for mobile applications and this is the reason im using lists over datagrid.
protected function sort_clickHandler():void
{
var dataSortField:SortField = new SortField();
dataSortField.numeric = true;
/* Create the Sort object and add the SortField object created earlier to the array of fields to sort on. */
var numericDataSort:Sort = new Sort();
numericDataSort.compareFunction=sortFunction;
/* Set the ArrayCollection object's sort property to our custom sort, and refresh the ArrayCollection. */
getAllMarkersResult.lastResult.sort = numericDataSort;
getAllMarkersResult.lastResult.refresh();
}
private function sortFunction(a:Object, b:Object, array:Array = null):int
{
var aPoi:LatLng = new LatLng(a.lat,a.lng);
var bPoi:LatLng = new LatLng(b.lat,b.lng);
var i:Number=GeodesicCalculatorUtil.calculateGeodesicDistance(FlexGlobals.topLevelApplication.currentLatLng2,aPoi,DistanceUnits.KILOMETERS);
var j:Number=GeodesicCalculatorUtil.calculateGeodesicDistance(FlexGlobals.topLevelApplication.currentLatLng2,bPoi,DistanceUnits.KILOMETERS);
return ObjectUtil.numericCompare(i, j);
}

Related

HierarchicalCollectionView: One time sort?

I have an AdvancedDataGrid that relies on a HierarchicalCollectionView as it's dataProvider. What I'd like to do is sort the data when it is first loaded, but then disable the sort so that anything that is added after the initial load doesn't cause the grid to auto-sort again. I tried something like this:
this._myDataProvider = new HierarchicalCollectionView(
new HierarchicalData(this._model.rootTasks));
var mySort:Sort = new Sort();
mySort.fields = [new SortField("startDate")];
this._tasksDataProvider.sort = taskSorting;
this._tasksDataProvider.refresh();
this._tasksDataProvider.sort = null;
But setting the sort to null just leaves the data unsorted. I guess what I'm asking is: how can I sort the underlying hierarchical data since it seems setting the sort property will keep it dynamically sorting. Thanks for any help you can provide.
Personally, I would change the sort order when you're getting the data. Either it's done on the server side or when you parse the data (ie. in your model). You can do a one time sort using Array with sortOn.
you can
1. sort the original data with sort function,
2. clone content and put it to a new collection with no sort (be careful do and make a manual clone),
3. just use the new data collection.
I had the same kind of problem until I realized that the sorting with Sort object does not change the "physical" ordering of the items within the Collection, so when you remove the Sort, the next refresh reverts the view to the actual "physical" ordering.
Similarily as stated above, I solved by it by cloning the sub-collections into sorted order this way:
public static function buildPositionSort():Sort
{
var dataSortField:SortField = new SortField();
dataSortField.name = "position";
dataSortField.numeric = true;
dataSortField.descending = false;
var sort:Sort = new Sort();
sort.fields = [ dataSortField ];
return sort;
}
/**
* This method is used to create a clone of ArrayCollection, because sorting does not
* actually change the physical ordering of the items.
*/
public static function createSortedArrayCollectionCopy(source:ArrayCollection):ArrayCollection
{
var target:ArrayCollection = new ArrayCollection();
source.sort = buildPositionSort();
source.refresh();
for each (var item:Object in source)
{
if (item.children != null) item.children = createSortedArrayCollectionCopy(item.children);
target.addItem(item);
}
return target;
}

How to sort AdvancedDataGrid with hierarchical data?

I have an AdvancedDataGrid with a HierarchicalCollectionView as its dataProvider. When I view the grid with the dataset I'm working with, and click the header of the column I wish to sort on, everything works perfectly. It sorts it hierarchically exactly how I would expect it to.
What I want to do now is have the grid already be sorted when it is shown to the user. Is there a way to do this programatically? I can't for the life of me figure out how to do this, and clearly it's possible since the AdvancedDataGrid has this built in.
Edit - BTW, I've tried this:
var myData:HierarchicalCollectionView = new HierarchicalCollectionView(theDataSource);
// Works fine using only the line above and clicking the header to sort.
// This is the part that I tried adding:
var sort:Sort = new Sort();
sort.fields = [new SortField("startDate")];
myData.sort = sort;
myData.refresh();
This appears to do something as far as sorting goes, but it doesn't sort it in the same way as clicking the column header. "startDate" is a property of an object in theDataSource by the way.
Looks like you want to sort dates. Sort can't do that out of the box. You have to use a compareFunction.
If your objects are of type Date it's quite easy:
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = ObjectUtil.dateCompare;
In case your column contains dates as strings you'll have to parse them first (code example from http://blog.flexexamples.com/2007/08/12/sorting-date-columns-in-a-datagrid/):
private function date_sortCompareFunc(itemA:Object, itemB:Object):int
{
/* Date.parse() returns an int, but
ObjectUtil.dateCompare() expects two
Date objects, so convert String to
int to Date. */
var dateA:Date = new Date(Date.parse(itemA));
var dateB:Date = new Date(Date.parse(itemB));
return ObjectUtil.dateCompare(dateA, dateB);
}
var sortField:SortField = new SortField("startDate");
sortField.compareFunction = date_sortCompareFunc;
Then just use the sortField like you did in your example. That should work fine.
You can create a new advanced data grid sort event and dispatch it on the grid after the hierarchical data is set on it (unfortunately I've had to use a callLater to give the grid time to deal with the collection internally it seems assignments to the dataProvider of the ADG are sometimes asynchronous)
var advancedDataGridEvent : AdvancedDataGridEvent = new AdvancedDataGridEvent(AdvancedDataGridEvent.SORT, false, true);
advancedDataGridEvent.columnIndex = columnIndex;
advancedDataGridEvent.dataField = dataField;
dispatchEvent(advancedDataGridEvent);
This code is from an extension of ADG so you would want the dispatchEvent to actually be on your instance of the grid if you're not creating an extension.
Also a note from the code:
//setting sortDescending=true on a column does not work as expected. so, until a solution
//is found, this works just as well. the event that is dispatch just tells the column
//to reset. so, one resorts ascending (the default), while a second resorts descending.
//however, this second is only dispatched if defaultSortDesc is true on the grid.
if (defaultSortDesc)
{
dispatchEvent(advancedDataGridEvent);
}
It dispatches the event twice to flip the sort.

Problem converting Array to Arraylist for dataProvider

private var MealsListResult:ArrayList = new ArrayList;
protected var _data:resultData = new resultData;
private function resultHandler():void
{
var Meals:Array = _data.Meals;
MealsListResult = _data.Meals as ArrayList;
MealDataGrid.dataProvider = Meals;
MealListView.dataProvider = MealsListResult;
}
Should this be working? the MealDataGrid is populating based on the array, but I am debugging and MealsListResult is null. but _data.Meals is not and I dunno if I'm missing something simple.
I can get it to work by doing it like: var MealsListResult2:ArrayList = new ArrayList(Meals); but I feel as though the first method should be working as well!
(there's mxml list and datagrid and such not shown here of course)
if _data.Meals is its runtime type is an array then _data.Meals as ArrayCollection will failed. but, new ArrayCollection(_data.Meals as Array) will working fine.
CMIIW
i guess your problem is you can't use single object as 2 or more different ui dataprovider.
try to use
MealDataGrid.dataProvider = _data.Meals;
MealListView.dataProvider = ObjectUtils.clone(_data.Meals);
UPDATE:
sorry i miss readed, i though it was ArrayColletion. but all you need to do is the same like ArrayCollection

Unwanted binding

The situation is simple. I have a datagrid that gets its data from a webservice.
When data from the webservice is retrived it calls the following function:
private function onListReg():void
{
arrRegOld = WSAutoreg.list.lastResult as ArrayCollection;
arrReg = WSAutoreg.list.lastResult as ArrayCollection;
dgReg.dataProvider = autoreglist;
}
dgReg is the Datagrid. the arr variables are ArrayCollections defined like so:
private var arrRegOld:ArrayCollection = new ArrayCollection;
[Bindable]
private var arrReg:ArrayCollection = new ArrayCollection;
The intent is when I hit a update button, it compares arrRegOld with arrReg and see if any values have changes. The problem is whenever I change values on the Datagrid it changes on both the dataProvider and on both ArrayCollections.
Does anyone know why is this happening? What should I do so that the binding applies only to one ArrayCollection?
Appreciate any tip.
- Mike
Your lists are sharing the same objects, if you modify the first element from arrReg you will see the modification also in arrRegOld - it is not related to binding. You need to clone the objects. You have several choices:
a) Implement a clone method for your objects (recommended)
b) Use a generic method like this one:
private function clone(source:Object):*
{
var array:ByteArray=new ByteArray();
array.writeObject(source);
array.position=0;
return(array.readObject());
}
and call arrRegOld = clone (arrReg); after arrReg = WSAutoreg.list.lastResult as ArrayCollection;

How do I programmatically associate a RadioButton with a RadioButtonGroup in ActionScript3?

I have a UI component that, for various reasons, I have to construct programatically. The component is a table of radio buttons grouped by column.
Right now, I'm constructing the column groups like so:
private function createGroupsForItemList(items: XMLList): void {
for each (var item: XML in items) {
var rbGroup: RadioButtonGroup = new RadioButtonGroup();
groups[item.#level.toString()] = rbGroup;
}
}
I'm trying to associate the RadioButton instances with the column groups like so:
private function createValueControl(item: XML): UIComponent {
var control: RadioButton = new RadioButton();
control.label = "";
control.group = groups[item.#level.toString()];
control.addEventListener(Event.SELECT, updateSelection);
return control;
}
I can see in the debugger that the control has an association to the group:
control.group == groups[item.#level.toString()]
However, I can see equally that the group does not know anything about the control:
group.radioButtons.length == 0
I imagine that this is because the setter for group in RadioButton is a dumb setter; all it does is copy to the variable, which doesn't do the magic that groupName does. However, I can't seem to find the value I should use to set the RadioButton.groupName property correctly.
So, in short, I'm stumped on how to get these bits to talk to each other. How do I do this?
-- EDIT --
It turns out that I can have the groups created and associated simply by setting the groupName property, but I can't get at the group to set up a selection listener; the group is NULL immediately after the setting process, which means that the second line below throws the Flex equivalent of an NPE:
control.groupName = groupNameForLevel(item);
control.group.addEventListener(Event.SELECT, updateSelection);
First instinct is that this issue has to do with invalidateDisplayList and when and how that is called. Of course, since issues related to that function are behind a number of Flex's quirks, I may just be scapegoating.
This is not the answer to your question per se, but it seems like it might actually work as an alternate solution.
RadioButtonGroups will initialize based on a IFlexDisplayObject. This means that you can do something like:
var c:HBox = new HBox();
var rbg:RadioButtonGroup = new RadioButtonGroup( c );
// do stuff with rbg.
c.addChild( new RadioButton() );
The problem is that it may not be the most practical answer, but it has the decided benefit of being a workable solution.
Setting groupName should work.
All I can suggest is to step through the group() getter of the RadioButton component and see where exactly it is failing. Are you programmatically creating the group too? If that's the case, maybe it isn't initialized fully yet.

Resources