Say I have the following custom component:
<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 prop:String;
private function formatProp() : String {
return "Hello, " + prop;
}
]]>
</fx:Script>
<s:Label text="User: {prop}"/>
<s:Label text="Greeting: {formatProp()}"/>
</s:Group>
If I add it to my application like this:
<local:MyComponent prop="Hello"/>
The result looks like:
User: Mark
Greeting: Hello, null
It seems Flex is setting prop on my custom component after it has already initialized the child labels, so it's reliant on the property changed event to set the user label.
Is there an elegant way to make Flex wait for all of my component's properties to be set before initially evaluating bindings?
Note: I realize the formatProp function is trivial and could be included inline, but this is just a simplified example.
The "elegant way" would be to actually provide data binding, so that you can change your property also afterwards. Your initial idea looked good, working with the answer provided by Cornel. I just wanted to mention this as your actual question sounded more like that you know your data binding is not working and you just wanted to postpone the initial setting of the variable.
Btw, should you plan to create custom components in Actionscript (instead of mxml) you'll face the opposite problem: properties are set before you had a chance to actually create your children, so you may need to buffer them if they actually should influence some childs properties.
it is not related to component livecycle, more to binding rules. Your function "formatProp" should recieve the parameter "prop" as a parameter in order to be called when the prop is changed. Try this code:
private function formatProp(props:String) : String {
return "Hello, " + props;
}
<s:Label text="Greeting: {formatProp(prop)}"/>
Related
I have extended the SkinnablePopUpContainer to make a popup window in mobile application. But i don't know how to pass a variable defined in the main view to this component. My code looks like the following
<fx:Declarations>
<fx:Component className="Alert">
<s:SkinnablePopUpContainer>
<s:Button label="OK" click="close()"/>
<s:SkinnablePopUpContainer>
<fx:Script>
<![CDATA[
private function setMetaDataXML(metaDataXML:XML):void{
var temp = metaDataXML;
}
]]>
</fx:Script>
</fx:Component>
</fx:Declarations>
--- main view continues
and here is how I call the component on a button click from the main view:
click="(new AlertMsg()).open(this, false)"
now i just want to call setMetaDataXML from the main view and pass the value. How can I achieve that? Thank you
I would recommend to create your component in a separate mxml file or even better in a pure AS3 based component. To do what you want to do, you could create a handler function in your Script area for that click event, and in there assign the instance of the component and then call any method before calling open, like:
click = "clickHandler()"
----- inside your script
function clickHandler : void {
var a : AlertMsg = new AlertMsg();
a.setMetaDataXML("foo");
a.open(this,false);
}
I would also recommend you to declare implicit setters and getters the AS3 way
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 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.
I have just implemented a dropdownlist of checkboxes taken from this ComboCheck example but made it extend DropDownList instead of ComboBox to provide better functionality that I required. I am attempting to create a DropDownList where some items are bold and non-checkboxes (or can be checkboxes) and others are not.
I have not been able to find anything online about doing this yet and have been trying to figure it out. I am currently using an ArrayCollection as a dataProvider but I think this could possibly be my issue and I should be trying to setup the labels in flex not AS3.
Does anyone know if this is possible? And if so do they have any links that could possibly help point me in the right direction?
Thanks.
EDIT: Code added for the itemRenderer, this worked I just need to specify each item that I want to be bold, though is there a better way to do this in the flex code as opposed to checking for a matching string in the renderer?
public class ComboCheckItemRenderer extends ItemRenderer{
public var item:CheckBox;
public function ComboCheckItemRenderer(){
super();
item = new CheckBox();
item.x = 5;
addElement(item);
item.addEventListener(MouseEvent.CLICK, onClick);
}
private var _data:Object;
[Bindable]override public function set data (value:Object):void {
if (value!=null) {
_data = value;
item.label = value.label;
if(item.label == "item1"){
item.setStyle("color","0x00ff00");
item.setStyle("fontWeight","bold");
}
item.selected = value.selected;
}
}
Edit 2: What I am ultimately trying to do is create a dropdown of checkboxes with data that I obtain from blazeDS that basically has a bunch of group titles and their corresponding sub-elements. I am trying to have the dropdown make the groups be in bold and to the left, and their sub-elements normal font and offset to the right. I also need to know when they are clicked whether it was a group header or sub-element, so that I can add them to an object that I will be sending back to my service to perform a sql query on.
ie.
[ ]**GROUP**
[ ] element
[ ] element
[ ]**GROUP**
[ ] element
What does your data look like? Why aren't you using MXML for this? Why are you overriding set data() as opposed to hooking the dataChange event? You are writing way more code than you need to here.
Lets look at it in a more "Flexy" way. Notice how I am using data binding for everything and conditionally setting the fontWeight based on the data that comes in. Anything more complicated should bust out to a function in the Script tag.
<?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">
<fx:Script>
<![CDATA[
protected function onClick(event:MouseEvent):void {
}
]]>
</fx:Script>
<s:CheckBox x="5" click="onClick(event)"
label="{data.label}" selected="#{data.selected}"
fontWeight="{data.label == 'item1' ? 'bold' : 'normal'}"/>
</s:ItemRenderer>
In light of your question you added in your edit, I would ask: What criteria are you using? You can put any function in your binding expression, so at a very minimum, you might do something like this:
<fx:Script>
<![CDATA[
private var itemsToBold:Array = ["label1", "label2"];
private function getFontWeight(label):String {
if(itemsToBold.indexOf(label) > 0)
return "bold";
return "normal";
}
]]>
</fx:Script>
<s:CheckBox fontWeight="{getFontWeight(data.label)}"/>
I'm running into an odd issue with itemRenderers inside a TileList.
Here is a working example without an itemRenderer: 152.org/flex/
Here is the broken version with an itemRenderer: 152.org/brokenExample/
(I don't have the rep to make both of these a link)
Both examples have "View Source" enabled.
To see the problem use the broken example, select an album and scroll down one row. Scroll back up and the images will be switched. If you try this on the working example it's fine.
This seems to be a widely known bug, but I can't find a solution for it.
UPDATE
I started playing with this example again and found out something else. Turns out you don't have to override the data setter. You can create a new method in the itemRenderer that is set whenever the tile wants to refresh. So the trick is to not rely on the initialize or creationComplete methods.
This is what I have for the itemRenderer in the Application.
<itemRenderers:ImageTile img="{data}"/>
This is the code I have in the itemRenderer.
public function set img(value:String) : void {
trace("setting source: " + value);
this.source = value;
this.name = value.toString().split("/").pop().split(".").shift();
}
I updated my example to reflex this change.
I don't have your app handy, so I can't test end-to-end, but I've looked at your source. You probably need to override the data setter in your itemRenderer:
<?xml version="1.0" encoding="utf-8"?>
<mx:Image xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
this.source = data;
this.name = data.toString().split("/").pop().split(".").shift();
}
private function init() : void {
// Removed from your source and transplanted above
}
]]>
</mx:Script>
</mx:Image>
Flex will attempt to re-use item renderers in lists (which means the lifecycle events you might be expecting -- initialize, creationComplete, etc. -- won't always fire), so if you want to be sure your renderer gets updated when the data item changes (as it will when scroll events happen), the best practice is to override the renderer's data property. That'll most likely fix the problem.
Maybe try to invalidate on creationComplete?
From what I recall with DataGrids (which work somewhat similarly to a tilelist), when an item comes into focus its recreated.
<mx:itemRenderer>
<mx:Image id="myImage" creationComplete="myImage.invalidate()" />
</mx:itemRenderer>
Haven't tried this code but I think this is where you want to start looking. I took a look at your itemRenderer component. Try creationComplete instead of initialize to call your function