Passing data and calling functions between host component and renderers in Flex - apache-flex

Is there any better way for passing data between component and its renderers (also calling methods)?
I am using dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or owner.dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or bubbling (depends on situation). And then listening in that component for my event.
And for passing data to renderer, I am using:
(myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data); or modifying dataProvider but then I have to callLater and call method inside renderer anyways.
This sucks, I have to callLater all the time for to wait for renderers set data function to execute, otherwise renderer is not recycled yet and is null.
Is there any more efficient, prettier way to transfer data and call functions between renderer and component?
P.S. And yes I have pass data to renderer. My renderer handles quite complicated logic.

I've resolved a problem similar to yours in this way:
var goalPropDLRenderer:ClassFactory =
new ClassFactory(GoalPropagationDataListRenderer);
goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode };
list.itemRenderer = goalPropDLRenderer;
Where: "list" is a list object for witch I've to set an item renderer; instead of setting the item renderer inline (on the "list" declaration via mxml), I've used this syntax. This code is called on the creationComplete of the "list" father (I think there are better places) In this way it's possible to pass "properties"/"values" to the renderer.
On GoalPropagationDataListRenderer I've:
[Bindable]
public var isPropagationMode : Boolean;
that gets populated by goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode }; so inside the renderer I can access the value of isPropagationMode without problems.
I hope it could help.

There are some ways, depends on what exactly you want to do (if the data are dependent to every row, or global in datagrid), but personally, I would avoid callLater and this you wrote: (myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data)
you can use bubbling event, then catch it in datagird (or whenever), and then get renderer through myRenderer(event.target), not needed to use getElementAt!, but questin is when would you dispatch the event (I dont know the details)
if you use global (in datagrid meaning) logic:
I would make some common object (called logic) which will be set in datagrid and renderers (separating logic from view is good practice anyway in my opinion)
then I would use the solution what Claudio wrote (setting properties to itemRenderer ClassFactory) to set the logic to renderer
or you can use listData object (but it's not as simple)
or if you write item renderer in mxml inside datagdid, you can easilly bind it using outerDocument like logic="{outerDocument.logic}"
or you can just use itemRenderer's owner property, so whenever yo want some methods, call like MyDataGrid(owner).doSomeDataGridCalculationsForData(data), not needed to create logic object if you dont want to

Related

Flex: NPE and Proper way to access component properties before creation complete

I think I'm missing something important about flex sdk component lifecycle, but cannot sort it out, although read a lot of tutorials. Could you please share your experience on how do you operate with flex visual object's properties and how do you avoid NPE when accessing them before component creation complete.
Let's say we have a simple component MyTitleWindow.mxml:
<s:TitleWindow>
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Another component got data from a remote object, and wants to apply the data into title window's datagrid and show it via PopUpManager:
private function handleDataReceived(data : ArrayCollection) : void {
var myTitleWindow : TitleWindow = new MyTitleWindow();
PopUpManager.addPopUp(myTitleWindow);
myTitleWindow.myDataGrid.dataProvider = data;
}
Ofcourse, the line myTitleWindow.myDataGrid.dataProvider = data will throw an NPE because we're trying to access myDataGrid that haven't been rendered yet.
Currently I can see only 2 options how to avoid NPE:
Create a setter for data in titleWindow, put the data into some
cache. Listen to creationComplete event, in it's handler apply data
from the cache to the datagrid. This approach works fine, but I'm
tired of adding this safe-guards across the application.
Make a
bindable property - works for me only with simple datatypes
(numbers, strings...)
Is there anything I'm missing in the area of using flex validation/invalidation cycle that could help to avoid excessive code?
The problem you are having and Crusader is reporting is because in Flex components are initialized lazy. This is generally a good thing but in your case it is what's causing your problems.
In general I wouldn't suggest to set the dataProvider on a view component from outside the component as you have no way of knowing if all is setup and ready to use.
What I usually do. In simple cases I simply add a public propberty which I make [Bindable]. The (i think) cleaner way would be to create a setter (and getter) and so save the dataProvider in a local variable (in your case probably ArrayCollection). In the setter I usually check if the "myDataGrid" exists and if it exists, to additionally set the dataProvider property. I would then add a CreationComplete callback in my component and in that I would also set the dataProvider.
So when setting the dataProvider before the component is finished initializing, the value would simply be saved in the local variable and as soon as it is finished setting up the dataProvider is automatically set. If the component is allready setup (you are changing the dataProvider) the setter would automatically update the dataProvider of "myDataGrid"
<s:TitleWindow creationComplete="onCreationComplete(event)">
...
private var myDataProvider:ArrayCollection;
private function onCreationComplete(event:FlexEvent):void {
myDataGrid.dataProvider = myDataProvider;
}
public function set myDdataProvider(myDataProvider:ArrayCollection):void {
myDataProvider = myDataProvider;
// Only update the dataProvider if the grid is available.
if(myDataGrid) {
myDataGrid.dataProvider = myDataProvider;
}
}
....
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Yeah, this can be an annoyance, and it's not just with popups. It can also be a pain when using a ViewStack components with a default creationpolicy, which I tend to do fairly often.
I might get flamed for this, but I usually just use bindings. I'm not sure what you mean by "simple datatypes" - it works fine with custom types too. You'd have to provide an example.
One thing you could do (and I'll probably get flamed for this :p ) is create your popup component instance early on and re-use it rather than creating a new one each time.
No, I don't think you're "missing something" in the component lifecycle.
I always try to invert the responsibility for setting a dataProvider and prefer to have components observe a [Bindable] collection.
In simple examples such as yours I avoid giving my components an id. This prevents me from breaking encapsulation by referring to them externally.
<s:TitleWindow>
<s:DataGrid dataProvider="{data}" />
</s:TitleWindow>
The consumers of your MyTitleWindow component should not know that it has a DataGrid with id 'myDataGrid', or be required to set the dataProvider property of 'myDataGrid'.
Considering components declared in MXML require a no-arguement constructor (and that we're unable to declare multiple constructors) - one approach that has worked well for me in the past is to offer a static 'newInstance' method. I give this method a relevant name based on the domain I'm working in, and also any required parameters.
public static function withData(data : ArrayCollection) : MyTitleWindow
{
var myTitleWindow : MyTitleWindow = new MyTitleWindow();
myTitleWindow.data = data;
return myTitleWindow;
}
This clearly communicates the 'contract' of my component to any and all consumers. (Obviously things become clearer with more relevant naming).
private function handleDataReceived(data : ArrayCollection) : void
{
PopUpManager.addPopUp(MyTitleWindow.withData(data));
}

Is there any difference of reset an ArrayCollection by set it's source or change it's reference

I have two ArrayCollection now (a, b) and a is set bindable. I want to reset a with b.
The old code in our project is like:
a = new ArrayCollection();
for (var i:int = 0; i < b.length; i++) {
a.addItem(b.getItemAt(i));
}
Then, I think it may cause a potential memory leak. So I changed it to:
a.removeAll();
for (var i:int = 0; i < b.length; i++) {
a.addItem(b.getItemAt(i));
}
Then I read a topic: Flex optimization tip: ArrayCollection.removeAll() vs. ArrayCollection.source = new Array(). Is this a bug ?
It says removeAll() will cause a performance problem when the data set is large.
So does it means there is a trick off? If the data set is small I should use removeAll, and if the data set is large, I should not use removeAll()?
Another question, I also read a topic about Changing the source of an ArrayCollection.
It says if directly use a = b, "it will kill all the databound controls that are listening to events on the ArrayCollection instance". I don't understand this. I tried a = b, and it works ok (the view that use a as dataprovider updates).
What's the difference between using a=b and a.source = b.source?
I'm new to Flex. Thanks in advance.
ArrayCollection is a wrapper class around Array, and underlying Array can be access using source property
The ArrayCollection class is a wrapper class that exposes an Array as
a collection that can be accessed and manipulated using the methods
and properties of the ICollectionView or IList interfaces. Operations
on a ArrayCollection instance modify the data source; for xample, if
you use the removeItemAt() method on an ArrayCollection, you remove
the item from the underlying Array.
so one should always use Source property of ArrayCollection, if have populated Array
i suggest to declare b as Array not as ArrayCollection and initialize a as
a = new ArrayCollection(b); or
a= new ArrayCollection();// Default constructor ArrayCollection(source:Array=null);
a.source = b; //updates data in Arraycollection
Data Binding means bound controls with datasource(could be any thing like functions, objects, other control, Array, XML, Collection, List etc)
Data binding is the process of tying the data in one object to another
object. It provides a convenient way to pass data between the
different layers of the application. Data binding requires a source
property, a destination property, and a triggering event that
indicates when to copy the data from the source to the destination. An
object dispatches the triggering event when the source property
changes.
Data Binding could be harmful for application with large data because it would creates multiple changes events and both getter and setter executes on change, which need extra proccessing so it would be good practice to shorter scope of a and provide data directly to source as
private function handler_B_DataChange(event:Event)
{
var a:Arraycollection = new ArrayCollection(b);
controlA.dataProvider = a;
//or just
controlB.dataProvider = new ArrayCollection(b);
}
Details of binding could be view on Binding to functions, Objects, and Arrays
Hopes that Helps
I would also try:
a.removeAll();
a.addAll(b.list);
When you declare:
a = new ArrayCollection()
it loses the pointer to the "old" ArrayCollection where it is binded to your application. Thus, that is why when you do "new ArrayCollection" the binding doesn't work anymore. However, in your example, you're not creating a "new ArrayCollection"... you're just replacing the objects in that ArrayCollection with something else... So the binding still works.
If you have data that is into the thousands, you might want to consider implementing a pagination of some sort. If it's just a couple hundred, then I don't think you need to worry too much about the performance of a.removeAll();

MXML-class initialization order

I have written a few custom components in Flex 4, and run into this issue a few times.
var myForm:MyForm = new MyForm;
myForm.SetData(data);
addElement(myForm);
Now Imagine I am calling these functions from a non-constructor function of a Panel or VGroup (or any other container). Annoyingly, during MyForm.SetData(), not all fields of myForm that are declared there are yet initialized. Such as:
<s:VGroup id="dataGroup">
If my SetData()-function wants to access dataGroup (for the reason to .addElement() the just recieved data to it), it just fails with a nullpointer exception, because dataGroup is not yet created, despite this being after the constructor. How can guarantee that the form was initialized fully?
Listening for the creationComplete event and adding your components in the handler for the event is one way to do this. This is what Sam DeHaan suggested.
Another way you can do this is to override the createChildren() function. This is the function that creates and adds all of a component's child components. Code would look something like this:
override public function createChildren():void
{
super.createChildren();
var myForm:MyForm = new MyForm;
// Note that data may be null here, best to
// override commitProperties() to set it.
myForm.SetData(data);
addElement(myForm);
}
The docs on the component lifecycle will provide a ton of detail on this topic.
Unless I'm misunderstanding your question,
You should put the code that runs into this null pointer exception in a creationComplete callback on the container that you need defined.

Flex: select tree node right after the dataProvider is been assigned / updated / replace

i have a Flex tree control and im trying to select a tree node 3 levels down right after the dataProvider is assigned with a collection object like the following.
basically treeItem1, treeItem2, treeItem3 are the nodes in the tree and treeitem3 is a child of treeItem2 which is a child of treeItem1. Assume these treeItem(1,2,3) are referenced correctly from the collection items.
my problem is that if i wait for the whole component to load completely then select the nodes, it open/select/scrolltoIndex correctly. However, if i were to select the node right after the dataProvider is assigned, then it doesn't even open or select (basically the this.treeService.selectedItem is always null).
can anyone point out what i did wrong? is there anything needs to happen after the dataProvider is assigned?
thanks
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
I have used the updateComplete event to know when a component (such as a DataGroup or List) has completed rendering after performing a simple task (such as updating the dataProvider reference). Of course, you have to be careful and remove listening to updateComplete because it can run a lot, unless you have a need for it to run.
Something like:
//...some function...
this.treeService.addEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
//...rest of some function...
private function onTreeUpdateComplete(event:FlexEvent):void {
this.treeService.removeEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
}
I'm not positive your experiencing the same issue but I seem to have the same type of problem with using the advanced data grid, it appears in these cases where the dataprovider is acceptable as multiple types, the components do some extra work in the background to wrap things up into something Hierarchical (HierarchicalData or HierarchicalCollectionView) and in doing so the dataprovider setter call is not synchronous (so it will return before actually having assigned the internal property storing the dataprovider). I've used callLater in this case with moderate success, callLater is generally a bad practice but basically adds a function to a list of functions to call once background processing is done, so this is assuming that something in the dataprovider setter called UIComponent.suspendBackgroundProcessing() and that it will subsequently call UIComponent.resumeBackgroundProcessing() and then it will execute the list of functions added by using callLater. Alternatively you could use setTimeout(someFunction,1000).
These are both "hacks" the real solution is to dig into the framework code and see what it's really doing when you tell it to set the dataprovider. Wherever you see that it actually has set the dataprovider you could extend that class and dispatch an event that you could listen for to run the function to do the selections after this point.
If anyone has a better solution please by all means correct me (I would love to have a better answer than this)

Flex 3 - Must I add components before setting their attributes when using AS3?

Let us say that I have a Flex 3 mxml component, call it A. A has a get/set attribute called 'b'. Within A I have another internal component C, which is specified using mxml. When "instantiating" component A within mxml, I can specify the value of b at declaration, and everything works fine. However, when I initialize the component using Actionscript, I must first add the component to a rendered container before I can set the attribute (in this case 'b') of said component. This happens when the setter for attribute 'b' somehow accesses C within A.
So, this fails at runtime (it says that C is null)...
var a:A = new A();
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"
this.addChild(a);
On the other hand, either of the following will work
<customNamespace:A b="woopy"/>
or
var a:A = new A();
this.addChild(a);
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"
As shown, no runtime error message is thrown when a attribute is set after a component is added to a container. Ok, this makes sense, I suppose the internals of the component are not actually created until the component is added to a container. Still, this is kind of annoying. Is there any way to guarantee that the component internals are fully rendered without adding it to a container? I don't like the way it feels different when I am using actionscript vs mxml. I want a solution so that basically declaring A in mxml with no attribute "arguments" is equivalent to declaring A using the new operator in AS. At least, in terms of the internal state of A.
To force a control to create its child controls you have to call the initialize method.
i.e. this should work :
var a:A = new A();
a.initialize();
a.b = "woopy";
this.addChild(a);
However, what I've been doing so far when declaring mxml controls is binding the internal controls to public variables declared in a script block. e.g.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
[Bindable]
public var labelText:String = "[Default]";
]]>
</mx:Script>
<mx:Label text="{labelText}"/>
</mx:Canvas>
This way you can set your parameters without having to worry about whether the controls have been created or not.
That's right -- if B's setter acts on C, you'll have problems, because when A's constructed, C's definitely not there yet, even if you've declared C in A's MXML.
About there being any way to guarantee a component and its children are fully rendered and usable without adding it to a container, the answer is no -- the framework won't perform its creation and rendering magic on a component until it's somehow added to the display list, either by way of MXML or addChild().
Of course, you could use the visible or includeInLayout properties (setting both to false on your A component, for example) to get around actually displaying the component, or if you had to do the instantiation in script, you could listen for either A's initialize or creationComplete events (both of which indicate A's children have been created and are ready to be acted upon), and just wait to set B until you receive that notification. As a general rule, though, I wouldn't advise calling the initialize() method directly; it's a framework method that gets called automatically just after addChild() anyway, and in general it's better to let the framework do its thing, rather than work around it.
var a:A = new A();
a.addEventListener(FlexEvent.INITIALIZE, a_initialize);
addChild(a);
private function a_initialize(event:FlexEvent):void
{
a.b = "woopy";
// ... and so on
}
So it goes, if you want to be sure.
Deepa Subramaniam, an engineer on the Flex team, recently posted an excellent video on her site covering the Flex component model in granular, step-by-step detail; I attended the talk at MAX where she recorded it, it was easily one of the best of the conference. Worth watching (and re-watching, and then watching again) for its detail and comprehensiveness. She actually addresses your question at several times during the talk. It's great stuff.
Best of luck!
To answer your main question, no, you don't have to add an AS3-instantiated component to the display list if you want to set its properties. There's no difference between creating it in MXML versus creating it in AS3... unless, of course, the component wasn't built properly.
The Flex team at Adobe (formerly Macromedia) spent many years refining optimizations for the Flex component architecture. There are two important parts of that design that are related to your problem:
They designed system of invalidation and validation so that you can set many properties at once, but the effects of the changes don't happen until you're done making all your changes.
When a component is first instantiated, it's children are not created right away. There's an optimal time to do it, and that's after the component has been added to the display list.
Basically, when you have a difference in behavior between an MXML instantiated component and an AS3 instantiated component, it's because the component was built without these two features in mind.
The component that is behaving improperly probably does something like this:
private var label:Label;
public function get b():String
{
return this.label.text;
}
public function set b(value:String):void
{
this.label.text = value;
}
The problem is that the component developer didn't take into account that the Label sub-component may not have been created yet! The best practice is to save the value in a variable and invalidate to pass it to the sub-component later (the validation cycle doesn't happen until after the component is initialized and the children are created).
private var label:Label;
private var _b:String;
public function get b():String
{
return this._b;
}
public function set b(value:String):void
{
this._b = value;
this.invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
this.label.text = this._b;
}
Alternatively, if you build an MXML component, you can do something similar, but it's often easier to use binding instead of the validation system:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="{this.b}"/>
<mx:Script><![CDATA[
private var _b:String;
[Bindable]
public function get b():String
{
return this._b;
}
public function set b(value:String):void
{
this._b = value;
}
]]></mx:Script>
</mx:Application>

Resources