Function binding not set after model is injected - apache-flex

I have a CustomDataGrid that extends from DataGrid and CustomDataGridColumn that extends from DataGridColumn.
CustomDataGridColumn has member variables of type Function.
Inside my view, I inject a presentation model using parsley.
The code is as follows:
<fx:Declarations>
<spicefactory:Configure/>
</fx:Declarations>
<fx:Script>
[Inject(id="associatedDocumentsPM")]
[Bindable]
public var model:AssociatedDocumentsPM;
</fx:Script>
<customDataGrid:CustomDataGrid id="AssocDocGrid"
width="100%" height="{(documentDataList.length+2)*20}"
doubleClickEnabled="true" enabled="{modeHandler.appEnable}"
dataP="{documentDataList}"
sortableColumns="false">
<customDataGrid:columnList>
<customDataGrid:CustomDataGridColumn
textAlign="left"
dataFieldIdentifier="documentName"
headerText="Document Type"
modifyLabelField="{model.modifyLabelField}"
dataField="documentName"
isNaNZero="true"
showDataTips="true"
editable="false"/>
...more columns here...
</customDataGrid:columnList>
</customDataGrid:CustomDataGrid>
The AssociatedDocumentsPM has functions defined and these are set in the columns.
One example being for attribute modifyLabelField="{model.modifyLabelField}"
CustomDataGridColumn.myLabelField is of type Function. myLabelField inside AssociatedDocumentsPM is a public function.
The Parsley Context file is in the parent of the above file and declares the PM as follows:
AssocDocPMFactory is a class with a sole function decorated with [Factory].
So the problem is the following:
When I debug the application and check the columnList of the DataGrid, the variable modifyLabelField is null.
Are function bindings treated differently than variables? I'm using Flex 4.5.1 together with Parsley 2.4.1
I understand that injection could happen after creationComplete is invoked but I thought the binding would take care of that.
I have a feeling that the model - the PM - is null until much much later and the function binding is not triggered.
I tried to use FastInject as well but to no avail.
Is this a problem with function pointers and Flex binding?

No it isn't. If you have these kind of doubts, it's always a good idea to quickly set up a simple isolated test situation that verifies your assumption. I created the following to test yours:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
creationComplete="onCreationComplete()" >
<fx:Script>
<![CDATA[
private function onCreationComplete():void {
test = function(item:*):String {
return "AAA";
}
}
[Bindable]
private var test:Function;
]]>
</fx:Script>
<s:List labelFunction="{test}">
<s:dataProvider>
<s:ArrayList>
<fx:String>A</fx:String>
<fx:String>B</fx:String>
<fx:String>C</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:List>
</s:Application>
If the test Function variable is declared Bindable, you'll see 3 times "AAA". If you remove the Bindable metadata, you'll see "A", "B", "C".
So clearly binding works with function pointers too (and you'll have to look elsewhere to find your nullpointer).

Related

FastInject not detecting objectId in Parsley

I have just started using Parsley recently and I ran into this issue. The thing is I have a custom component in my project, which is "configured" by Parsley and has a piece of code as follows:
<fx:Script>
<![CDATA[
...
[Inject(id="dateFormatter")]
[Bindable] public var dateFormatter:DateFormatter;
...
]]>
</fx:Script>
<fx:Declarations>
<parsley:Configure />
</fx:Declarations>
My problem is that I don't want Parsley to configure the component entirely. I want to simply use FastInject in MXML, instead of using Configure, like:
<parsley:FastInject objectId="dateFormatter" property="dateFormatter" type="{DateFormatter}" />
From what I found when I searched online, the objectId in FastInject is the same as [Inject(id="dateFormatter")]. Here's the source for that. Please correct me if I am wrong :).
But when I use it, I hit the following error:
Error: More than one object of type mx.formatters::DateFormatter was registered
Does this mean that the ID of the property being injected is not being picked up? It works fine when I configure the whole component and use the Inject meta-tag, but I don't want to configure the whole component.
Can someone suggest a solution?
FastInject by id works if objects declared in the context have an id.
Context configuration
<fx:Declarations>
<foo:FooBar1 />
<foo:FooBar2 id="fooBar2" />
</fx:Declarations>
FastInject in your component
<fx:Declarations>
<parsley:FastInject injectionComplete="handlerInjectComplete(event)">
<parsley:Inject property="foobar1" type="{FooBar1}" />
<parsley:Inject property="foobar2" objectId="fooBar2"/>
</parsley:FastInject>
</fx:Declarations>
<fx:Script>
<![CDATA[
[Bindable]
public var foobar1:FooBar1;
[Bindable]
public var foobar2:FooBar2;
protected function handlerInjectComplete(event:Event):void
{
if(foobar1) trace("foobar1 available");
if(foobar2) trace("foobar2 available");
}
]]>
</fx:Script>
This works for me.
Parsley FastInject gets confused when you inherit B from class A and want to inject both by id, while specifying type.
You need to use only one of objectId / type attributes of FastInject

How can I access a component within <fx:Declarations> in an MXML class?

Is it possible to access a declared component as an IFactory within an MXML class? I've used this style of declaring factories many times for Skin Parts, but I've never figured out how to access those factories from within the MXML.
Here's an example of what I would expect to work:
<fx:Declarations>
<fx:Component id="labelDisplay">
<s:Label fontSize="12" fontWeight="bold"/>
</fx:Component>
</fx:Declarations>
<fx:Script>
<![CDATA[
override protected function createChildren():void
{
super.createChildren();
var label1:Label = labelDisplay.newInstance();
addElement(label1);
var label2:Label = labelDisplay.newInstance();
addElement(label2);
var label3:Label = labelDisplay.newInstance();
addElement(label3);
}
]]>
</fx:Script>
* edit *
The reason I was hoping the above code would work is based on the way dynamic Skin Parts are handled in the Spark skinning architecture. If the above code were a part of an MXML skin class, then in my host component, I could have the following.
[SkinPart(required="true",type="spark.controls.Label")]
public var labelDisplay:IFactory;
In the Spark skinning architecture, at what point does the <fx:Component> turn into an IFactory?
I dug into Flex's SkinnableComponent to find the solution based on how they tie an MXML Skin to an AS HostComponent. Apparently, even though "labelDisplay" doesn't show up in FlashBuilder's autocomplete as a concrete class member, you can still reference it as a dynamic property. I've modified my original example here:
<fx:Declarations>
<fx:Component id="labelDisplay">
<s:Label fontSize="12" fontWeight="bold"/>
</fx:Component>
</fx:Declarations>
<fx:Script>
<![CDATA[
override protected function createChildren():void
{
super.createChildren();
var labelFactory:ClassFactory = this["labelDisplay"];
var label1:Label = labelFactory.newInstance();
addElement(label1);
var label2:Label = labelFactory.newInstance();
addElement(label2);
var label3:Label = labelFactory.newInstance();
addElement(label3);
}
]]>
</fx:Script>
The problem I see is that you didn't create an iFactory, so won't be able to access iFactory methods.
I would recommend you drop into ActionScript and actually create an iFactory if you need one. This uses the ClassFactory class
protected var labelDisplay: iFactory = new ClassFactory(spark.controls.Label);
Then your createChildren code should work as is.

Access objects in custom components file in flex

I am pretty new to flex, specially to mxml part. There are some confusing things for me.
For example I have custom component file Abc.mxml with code:
<s:Group>
//Lots of code
<s:Button id="someId" /*code*/ />
</s:Group>
And then I have Xyz.mxml which is also custom component file.
<s:Group>
<fx:Scrip>
//something happens here, for example some other button click or whatever
</fx:Script>
//code
<comp:Abc />
</s:Group>
So question is how do I access that button's properties. I want when something happens in Xyz file, button's (someId) visibility to become false. If Abc.mxml was AS class file then it would be easy, just make object etc., but how to get if it's mxml file, I have no idea.
There's no big difference between mxml and as. When you write Xyz.mxml:
<s:Group>
<fx:Scrip>
//something happens here, for example some other button click or whatever
</fx:Script>
//code
<comp:Abc />
</s:Group>
...you just specify class Xyz derived from Group. Mxml - is just markup language which makes building interfaces easier. During compiling mxml files are transformed to pure AS, so there's nothing (on a large scale) you can do in mxml which you can't in AS and vice-versa.
ID property in mxml is similar to instance name in AS, i.e. it will be converted to public property in your calss.
Answer to your question.
You can write public function in Abc and call it in Xyz.
Abc.mxml:
<s:Group>
<fx:Script>
<![CDATA[
public function doSomething():void
{
someId.enabled = false;
}
]]>
</fx:Script>
<s:Button id="someId"/>
</s:Group>
Xyz.mxml:
<s:Group>
<fx:Script>
<![CDATA[
private function somethingHappened():void
{
abcComponent.doSomething();
}
]]>
</fx:Script>
//code
<comp:Abc id="abcComponent"/>
</s:Group>
In somethingHappened function you can access Button abcComponent.someId directrly, but I strongly reccommend not to do this, since it breaks encapsulation and makes your classes more cohesive (and so on).

Get variable from ItemRenderer to Main Application

I have a List with TextInput as item renderer. I want to get the value entered in the TextInput (form the TextInputItemRenderer) and pass it the main application to do some checks(upon tapping enter on the textInput -- see code below).
I know that we can do it thru dispatching event but I still don't understand how to pass a variable from the ItemRenderer to the main app.
Help Pls.
Thanks
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true" xmlns:components="components.*" width="100%"
>
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
override public function set data( value:Object ) : void {
super.data = value;
}
protected function myTextInput_enterHandler(event:FlexEvent):void
{
trace(myTextInput.text);
What Next??
}
]]>
</fx:Script>
<components:ClearableTextInput text="{data.label}" id="myTextInput" enter="myTextInput_enterHandler(event)"/>
</s:ItemRenderer>
i'm not sure if I got your question correctly but would this help?
http://www.ajibanda.blogspot.com/2011/02/changing-currentstate-of-main-and.html
Instead of trying to access from MainApp to itemRenderer, i think you can do backward. Follow one of two solutions below:
In itemRenderer, assign value you want to check later to a public global variable on the MainApp. The limitation is you then only enable to check it on MainApp, not any where esle (other itemRenderer, component, module etc.)
Use EvenBus to put the value to a global container. Create a static eventBus instance in AppUtils, for example. In itemRenderer, AppUtils.eventBus.dispatch() an event with the value attached to it each time the value changed. And then use AppUtils.eventBus again to addEventListener() to retrieve the value and check wherever you want. Google AS3Commons for EventBus.

Disable warning only in one place

In an MXML code
<fx:Script>
public var data:ArrayCollection = new ArrayCollection();
</fx:Script>
<s:DataGroup dataProvider="{data}" />
I'm getting a warning:
Data binding will not be able to detect assignments to "data"
I know that the data provider will be never changed in this case, and want to suppress this warning in this case, but I don't want to completely disable it, -show-binding-options=false in all project is not an option.
How to disable a warning only in one place? Disabling for the whole file is not so good, but acceptable.
How about just making your data variable bindable? Something like:
<fx:Script>
[Bindable]
public var data:ArrayCollection = new ArrayCollection();
</fx:Script>
<s:DataGroup dataProvider="{data}" />
Instead of using <fx:Script></fx:Script> you could use <fx:Declarations></fx:Declarations>. Any object declared in that MXML element is bindable implicitly. Here's how your code will look like then:
<fx:Declarations>
<s:ArrayCollection id="data" />
</fx:Declarations>
<s:DataGroup dataProvider="{data}" />
Additionally it becomes much more readable and there's no mix of ActionScript and MXML. Because your collection is declared as public it makes difference whether to use ActionScript with [Bindable] or using MXML.
BTW, a general recommendation for cleaner code is to separate ActionScript completely from MXML. For instance in my projects I create a separate ActionScript file for each MXML component in the form <NameOfComponent>Includes.as.

Resources