I have an ArrayCollection we'll call "Items". It is essentially a flat collection of hierarchical data (each item has Parent and Children properties). I want an AdvancedDataGrid to display the data in hierarchical form, so essentially I could just do this and it would display fine:
// Note: RootItems would be an ArrayCollection that is updated so only the
// top level items are within (item.Parent == null).
var hd:HierarchicalData = new HierarchicalData(model.RootItems);
var hcv:HierarchicalCollectionView = new HierarchicalCollectionView(hd);
myDataGrid.dataProvider = hdc;
This works, but I want to be able to see updates in myDataGrid when the Items collection is updated (since RootItems won't pick up updates to any children, only the top level tasks). Is there any easy way to do this? I'm guessing I'll have to create a class that extends HierarchicalData and somehow alert it when Items changes, but that sounds like it'll be pretty slow. Thanks in advance for any help you can provide!
You have two options to solve this problem. Either you create your own implementation of IHierarchicalData (it doesn't have to extend HierarchicalData and in this particular case there won't be much code you can reuse) or you change the way you handle your data a little bit so that it fits into the standard use case:
[Bindable] // make it bindable so that the HierarchicalCollectionView gets notified when the object changes
class Foo // your data class
{
// this constructor is needed to easily create the rootItems below
public function Foo(children:ArrayCollection = null)
{
this.children = children;
}
// use an ArrayCollection which dispatches an event if one of its children changes
public var children:ArrayCollection;
// all your other fields
}
// Create your rootItems like this. Each object can contain a collection of children
// where each of those can contain children of its own and so forth...
var rootItems:ArrayCollection = new ArrayCollection([
new Foo(
new ArrayCollection([
new Foo(),
new Foo(),
new Foo(
new ArrayCollection([
// ...
]),
new Foo()
])
),
new Foo(
// ...
),
// ...
]);
// Create the HierarchicalData and HierachicalCollectionView
var hd:IHierarchicalData = new HierarchicalData(rootItems);
[Bindable]
var hcv:IHierarchicalCollectionView = new HierarchicalCollectionView(hd);
Then you can use hcv as dataProvider in your ADG and use its methods to add and remove items. The ADG will refresh whenever you add, remove or update an item.
I suggest you do it the standard way unless that's really not possible.
Related
I want to use single collection object to two different UI components. 1. Datagrid and 2nd is chart component. I dont want to change anything inside the arraycollection object but I want to use it at the same time with two different component with minor changes. I know we can use filter function some how but not sure how to apply filter to arraycollection object so that one component (datagrid) can use the original arraycollection object and second component (chart) used the modified one.
Thanks,
If you use the same ArrayCollection as the dataProvider for two different components, then any filter or sort applied to that ArrayCollection will show up in both components.
What you want to do cannot be done.
However, you can create multiple ArrayCollections based on the same source and apply filters to them differently. Conceptually something like this:
public var arrayCollection1 : ArrayCollection = new ArrayCollection();
public var arrayCollection2 : ArrayCollection = new ArrayCollection();
protected function onIGotTheArray(value:Array):void{
arrayCollection1.source = value;
arrayCollection2.source = value;
dataGrid.dataProvider = arrayCollection1;
chart.dataProvider = arrayCollection2;
}
Now you can apply a filter to the first arrayCollection without affecting the second arrayCollection, or vice versa.
This is the preferred approach in my experience.
I'm working (for my sins) on a Flex 3.3 project, which unfortunately cannot be upgraded to a newer SDK version at this stage, and have hit an issue with the custom hierarchical tree class (subclassing mx.controls.Tree) we're using. Excuse the spelling; the previous developer had a fear of dictionaries...
public class HierachyTree extends Tree
public function HierachyTree()
{
super();
iconFunction = itemIconFunc;
// etc.
}
I'm using a solution somewhere between these two methods (basically, implementing ITreeDataDescriptor) in order to add live text filtering to the component, and it's working so far:
public class HierachyTreeFilteredDataDescriptor implements ITreeDataDescriptor
{
private var filter:Function
public function HierachyTreeFilteredDataDescriptor(filterFunction:Function)
{
this.filter = filterFunction;
}
public function getChildren(node:Object, model:Object=null):ICollectionView
{
var children:ArrayCollection = new ArrayCollection([]);
// Filter the children...
return children;
}
public function hasChildren(node:Object, model:Object=null):Boolean
{
var treeItem:Object = node as Object;
if (! (treeItem is ScenarioMeta)) return (treeItem as Object).children.length > 0;
else return false;
}
The issue is that (with tree:HierachyTree) neither tree.maxVerticalScrollPosition nor the protected property tree.verticalScrollBar.maxScrollPosition updates when the search string is changed.
I've tried calling invalidateList() and invalidateDisplayList() on tree — and calling invalidateDisplayList() and invalidateSize() on tree.verticalScrollBar — to no avail.
Any ideas?
I have completely the same situation. I have a need to filter the whole tree, and I did use the solution from those 2 blogs.
Tried to validateList(), validateDisplayList(), tried to return a new collection(not filtered) on getChildren in data descriptor, but that caused other issues.
The following was the easiest and worked for me the best:
treeDataProvider.dispatchEvent(new
CollectionEvent(CollectionEvent.COLLECTION_CHANGE, false, false,
CollectionEventKind.RESET));
So, let me get this straight, what you're trying to accomplish is filter the data as per a search string inserted which should then refresh the tree?
If that's the case, it's fairly simple as long as you're using ArrayCollection as the data provider for the tree:
// Check if data is ArrayCollection
var ac:ArrayCollection;
if(tree.dataProvider is ArrayCollection)
{
ac = ArrayCollection(tree.dataProvider);
}
else if(tree.dataProvider is HierarchicalData) // Check if it's hierarchical data
{
ac = HierarchicalData(tree.dataProvider).source as ArrayCollection;
}
// filter - specify custom filter function somewhere, look at docs on how to implement
ac.filterFunction = someFilterFunction;
ac.refresh(); // Does the filtering and lets the tree know that it should redraw all nodes
I think you get the idea. Much easier to do this based on the data.
If I have a flex component that is a general popup, it's basically just a white popup that I pass an Array named "modules" to.
For instance:
var array:Array = ["mainArticle","title"];
or
var array:Array = ["creditCard"];
These are two examples that I might pass in. The first one would add my modules to the popup so the popup will be used for editing an "article." The second would add the Credit Card Change module, which would be a form that would allow the user to update their credit card information.
My question resides in the dataProvider for this popup. If I am passing in the article updater, I need a dataProvider that contains information like "font," "color," "size," etc. If I am passing in the credit card updater, I need a dataProvider that contains information like "number," "securit code," "expiration date," etc.
I could have a dataProvider class that has all of the information and only sets what I need, but it could get huge if I did something like:
public class myDataProvider {
public var mainTextFont:String;
public var mainTextSize:int;
public var mainText:String;
public var cardNumber:String;
public var cardExpiration:Date;
public var cardSecurity:String;
}
This is sort of an abstract idea, but I am looking for a solution that allows me to give my popup dataproviders without using one central dataProvider that would have a copy for every possible situation.
Thanks!
The simplest way to approach this is to create different dataProviders, depending on the class. My main popup has a "ModuleList" (custom list of strings) and it adds "modules" (not an actual flex module) to itself, giving each one the correct type of dataProvider.
public var recipientList:RecipientList;
private function setupModules():void {
for each( var s:String in moduleList ){
switch( s ){
case 'recipients':
var recipients:Recipients = new Recipients();
recipients.list = recipientList;
break;
case 'article':
// Article Logic
break;
case 'creditCard':
// Credit Card Logic
break;
}
}
}
This is just a generic idea, I was wondering what the best way to do this was, and this is how I decided to approach it.
Would you consider use a generic Object class and wrap any param you need in the Object?
var data:Object = new Object();
data.mainTextFont = "";
data.mainTextSize= "";
data.mainText= "";
For component, you may also consider use "State" value to control the display/layout.
Hope this helps.
I have a flex application with two objects: a parent widget (called an IBaseWidget) and a child widget (called a HelperWidget2). When the user clicks on a help link, a helper widget is loaded into the list of base widgets and then displayed for the user.
However, when I try to access this child widget by casting the base widget in the collection to the child widget type, the child widget returns null and I am unable to work with the widget.
The following snippet correctly returns the widget ID of the newly added widget and dispatches an event to load the widget:
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
ViewerContainer.dispatchEvent(new AppEvent(AppEvent.WIDGET_RUN, id, openQuickQueryCanvas));
Once the widget is loaded, a callback function called openQuickQueryCanvas() attempts to do another action with the helper widget:
private function openQuickQueryCanvas():void{
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
var bWidget:IBaseWidget = WidgetManager.getInstance().getWidget(id) as IBaseWidget;
var helperWidget:HelperWidget2 = bWidget as HelperWidget2;
if(helperWidget != null){
helperWidget.quickQueryCanvas.dispatchEvent(new MouseEvent(MouseEvent.CLICK));//fire an event to open the quick query canvas
}
}
The problem is that helperWidget above always returns null, meaning the cast isn't successful. This doesn't make sense to me, because bWidget is of type HelperWidget2.
Any thoughts? I'm stumped...
First off, make sure that HelperWidget2 implements IBaseWidget like so
public class HelperWidget2 implements IBaseWidget
Second, I would suggest using the is keyword instead of casting and checking for null:
private function openQuickQueryCanvas():void {
var id:Number = WidgetManager.getInstance().getWidgetId("Helper");
var bWidget:IBaseWidget = WidgetManager.getInstance().getWidget(id) as IBaseWidget;
if(bWidget is HelperWidget2)
{
HelperWidget2(bWidget).doWhatever();
}
}
Cast the returning instance as an object, instead of HelperWidget2. You won't have intellisense for the methods at design time, but more importantly, it won't be null at run time.
var bWidget:Object = WidgetManager.getInstance().getWidget(id);
bWidget.doWhatever();
How can I populate multiple datagrids in flex with a single datasource that is filtered differently for each datagrid. I'm assigning the event.result from my remote object call to three different array collections, each with its own filter function. When I assign and refresh the filter functions, they each affect all array collections. So, the results of the last array collection refresh end up in all three datagrids.
You probably need to use ObjectUtil.copy on your event result to have 3 separate ArrayCollections, one for each DataGrid... otherwise they all point at the same memory location of the single ArrayCollection and any changes made to it will be reflected in all DataGrids.
var AC1:ArrayCollection = event.result as ArrayCollection;
var AC2:ArrayCollection = ObjectUtil.copy(AC1) as ArrayCollection;
var AC3:ArrayCollection = ObjectUtil.copy(AC1) as ArrayCollection;
I would make copies of your data provider, ie:
var myDataArray:Array; // this contains your original data.
dataGrid1.dataProvider = new ArrayCollection(myDataArray.concat());
dataGrid2.dataProvider = new ArrayCollection(myDataArray.concat());
dataGrid3.dataProvider = new ArrayCollection(myDataArray.concat());
The solutions that have been provided may not behave as you might wish they would. An ArrayCollection technically consists of a model and a "view" into the model. In my understanding, both the solutions that have been provided create a copy of the model. This means if you add an item to one ArrayCollection it won't show up in another regardless of whether it would match that ArrayCollection's filter. Usually you want it to be a part of the model of the other ArrayCollections as well but only be visible if the added item passes the respective ArrayCollection's filter. You can share the "model" amongst ArrayCollections while having separate views into the model like so:
var collection1:ArrayCollection = new ArrayCollection();
var collection2:ArrayCollection = new ArrayCollection();
collection2.list = collection1.list;
var collection3:ArrayCollection = new ArrayCollection();
collection3.list = collection1.list;
Now you can add an item to any of the three collections and it will show up in the others. However, you can have separate filters and sorts on each individual ArrayCollection and that won't affect what's viewable in the others. You can read more about this here:
http://aaronhardy.com/flex/collections-and-chaining-for-separate-presentation/