Assign variable to MXML component ID - apache-flex

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?

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

Function binding not set after model is injected

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).

Flex 4 Custom Component - How to notify skin of property changes?

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

What is the MXML syntax to assign properties of subcomponents in custom MXML Components?

I am working on a custom Flex 4 component which is an aggregation of two existing flex components. I would like to be able to specify my own custom properties for the component as well as access the existing public subcomponent properties via MXML. For instance I might want to adjust the font color or style for the label and text input.
A toy component which aggregates both a label and a text input:
<?xml version="1.0" encoding="utf-8"?>
<s:Group 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[
[Bindable] public var prompt:String = "default prompt";
[Bindable] public var input:String = "default inpput";
]]>
</fx:Script>
<s:VGroup>
<s:Label id="cLabel" text="{prompt}" />
<s:TextInput id="cTextInput" text="{input}" />
</s:VGroup>
</s:Group>
Then in my main application I would like to access the public interfaces of the sub-component via mxml without re-writing a pass-through binding for every one. Something like:
...
<local:myInput prompt="name" input="please enter name">
<local:cLabel color="0xffffff" />
<local:CTextInput fontStyle="bold" />
</local:myInput>
In actionscript one can do this easily for all public properties:
myInput.cLabel.color = "0xffffff";
But I am stumped on the syntax for MXML. This seems like it should be easy, but I have not found the answer yet. Any help greatly appreciated.
You can't daisy chain down an display hierarchy w/ the MXML tag/value. You can do it in ActionScript, as you specified, but even that would probably be considered a bad practice.
I'll point out that color on the Label and fontStyle on the TextInput are not properties. They are styles So, the code you have:
myInput.cLabel.color = "0xffffff";
Would most likely throw an error because color is not a property. You'd have to use code like this:
myInput.cLabel.setStyle('color',"0xffffff");
However, since styles are usually inherited by the children; I suspect at the top level component, you can set the style and it would immediately trickle through to the children automatically. So, you should just be able to do:
myInput.setStyle('color',"0xffffff");
Or in MXML:
<local:myInput prompt="name" input="please enter name" color="0xffffff" fontStyle="bold" >
</local:myInput>
And it should trickle on down. Things can get trickier if you want to set styles individually on child components.
But, back to your original question w/ regards to properties. To keep a component encapsulated, you should create properties that set on the children. Something like this:
private var _property : String;
public function get property():String{
return _property;
}
public function set property(value:String){
_property = value;
myChildComp.property = value;
}
It can suck if you need to do this for a lot of properties. If encapsulation of this component isn't a priority; just set them in ActionScript.

Binding the value of a variable to a text input

I've been looking all over the internet but I couldn't find an answer to the following question: Is it possible to have the value of some string variable bound to the text inside a text input control? To clarify, whatever text was entered into the text input would be stored as the value of the variable.
Yes. Simply enough, you can do 2 way binding:
<fx:Script>
<![CDATA[
[Bindable] private var someObject:Object;
]]>
</fx:Script>
<s:TextInput text="#{someObject.someString}" />
With 2 way binding, any changes to the property 'someString' will change the TextInput or if you change the TextInput manually, your property 'someString' will be updated.
This may not be exactly what you're looking for but you could always have an event handler on the focusLostevent from the textInput which sets the variable you want to the textInput.text
You could set the value of the variable using the change event of the TextInput.
Example (untested):
<fx:Script>
<![CDATA[
[Bindable] public var someText:String = "foo";
protected function myText_changeHandler(event:TextOperationEvent):void
{
someText = myText.text;
}
]]>
</fx:Script>
<s:TextInput id="myText" text="{someText}" change="myText_changeHandler(event)" />

Resources