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
Related
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).
I have a custom Flex 4+ component that I am trying to make and have the skin be aware of changes to a custom property. This property will determine the graphic on the button (and some other visual changes) but the data will change constantly as it will be updated by a timer.
I've looked at untold examples and still seem unable to get the syntax correct or discover how things should be separated. I've looked at overriding commitProperties and the PropertyChangeEvent without success. So I have two questions.
1) How can I get a skin to be notified of a bound property when it changes?
2) If the data for a bound property of the component is an object, will binding work properly if a property of the object changes (or would it be better to pass each property separately)?
Here is a stripped down example of what I'm trying to achieve.
The component looks like this:
<s:ButtonBase xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
}
]]>
</fx:Script>
I'm calling it like this:
<components:MyButton id="myButton" iconData="{myData.curIconTag}" skinClass="skins.MyButtonSkin" />
I have a lot of different images I could be loading and so I'm afraid the number of states (with the combinations of up/down/over/disabled, etc. may get out of hand so the SetIconDisplay is setting the icon, but the real key is that I have other code in that function that needs to execute when the iconData property changes every X minutes or so. So the skin is something like this:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
creationComplete="init()">
<fx:Metadata>
[HostComponent("components.MyButton")]
</fx:Metadata>
<s:states>
<s:State name="default" />
<s:State name="down"/>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="disabled" />
</s:states>
<fx:Script>
<![CDATA[
import components.MyButton;
[Embed(source="images/image1.png")]
private var icon1:Class;
[Embed(source="images/image2.png")]
private var icon2:Class;
[Embed(source="images/image3.png")]
private var icon3:Class;
[Bindable]
public var hostComponent:MyButton;
[Bindable]
private var iconClass:Class;
private function init():void
{
iconClass = new Class();
}
// how do I get this called when the iconData property on my custom component is changed?
private function SetIconDisplay():void
{
switch (hostComponent.iconData)
{
case "apple":
iconClass=icon1;
break;
case "orange":
iconClass=icon2;
break;
case "grape":
iconClass=icon3;
break;
}
}
]]>
</fx:Script>
<s:BitmapImage source="{iconClass}" x="0" y="0" width="180" height="108"/>
Again, don't worry as much about how the skin is actually doing what it is doing as that will probably change (not using states). I'm just trying to figure out how to call a specific function when the bound property is changed.
Thank You!
I ended up dispatching a custom event when the data is updated and listen for it in the skin.
The component:
<s:ButtonBase xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import classes.CustomEvent;
private var _iconData:String;
[Bindable]
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
dispatchEvent(new CustomEvent("iconDataUpdated"));
}
]]>
</fx:Script>
The skin adds this:
protected function skin_preinitializeHandler(event:FlexEvent):void
{
hostComponent.addEventListener(CustomEvent.ICON_DATA_UPDATED,SetIconDisplay);
}
Having the base class call a function on one particular skin can get awkward, as it means that the base class is dependent on the skin class, which makes it difficult to swap out skins. There are two good ways to get around this:
Option 1: Move the iconClass up into the component. The skin class can bind directly to the property, and the logic for deciding which icon to use can be handled by the component instead of the skin. This keeps logic out of the skin, and keeps the amount of skinning code you have to work with down.
Option 2: Add an iconData property to the skin, and bind it to the iconData property of the host component. In the setter function, call SetIconDisplay when you have a valid value. This keeps the icons encapsulated in the skin, which may help if you want to use a very different skin for the same component.
Edit: If you're planning on creating several other skins that don't use the icons, #2 is the way to go. Create a property on the skin like so:
private var _iconData:String;
public function get iconData():String
{
return _iconData;
}
public function set iconData(value:String):void
{
_iconData = value;
SetIconDisplay()
}
Then use a binding to connect it to the hostComponent:
<fx:Binding source="hostComponent.iconData" destination="iconData" />
Another solution to the general question, though maybe not ideal in this situation, is to call skin.invalidateDisplayList() whenever a property changes. Then, in the skin, override the updateDisplayList function and from there call a function that reacts to the changed properties, as well as calling the function on the parent class obviously.
See here: https://forums.adobe.com/thread/797247
<s:BitmapImage source="{hostComponent.iconClass}" />
should work
you don't need to declare public var hostComponent:MyButton;
it's part of SparkSkin
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).
I have my custom component and for example few Label. I want to pass to my component value which will be assign to label's id.
Code:
<fx:Script>
<![CDATA[
[Inspectable]
[Bindable]
public var test:String = "asd";
]]>
</fx:Script>
<s:Label id="{test}" text="etc"/>
Error: {test} is not a valid identifier
Can I even do something like that?
No you can't. You have to understand that when you write an mxml component like
<s:Group>
<s:Label id="myLabel" />
</s:Group>
it will generate ActionScript code like
public class MyClass extends Group {
public var myLabel:Label;
}
(Mind you, I grossly oversimplify the code here to convey the most important part).
As you can see your 'id' is in fact a property name. And you can't change a property's name at runtime can you?
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.