Access objects in custom components file in flex - apache-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).

Related

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.

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.

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.

How to inherit states with mxml?

I have the following panel component called AdvancedPanel with controlBarContent:
<!-- AdvancedPanel.mxml -->
<s:Panel>
<s:states>
<s:State name="normal" />
<s:State name="edit" />
</s:states>
<s:controlBarContent>
<s:Button
includeIn="edit"
label="Show in edit"
/>
<s:Button
label="Go to edit"
click="{currentState='edit'}"
/>
</s:controlBarContent>
</s:Panel>
I created a second panel, called CustomAdvancedPanel based on the AdvancedPanel since I don't want to redeclare the controlBarContent
<!-- CustomAdvancedPanel.mxml -->
<local:AdvancedPanel>
<s:Button includeIn="edit" label="Extra edit button" />
</local:AdvancedPanel>
This doesn't work, because the 'edit' state in CustomAdvancedPanel isn't declared according to the compiler. I have to redeclare the edit state in CustomAdvancedPanel.mxml as follows:
<!-- CustomAdvancedPanel.mxml with edit state redeclared -->
<local:AdvancedPanel>
<local:states>
<s:State name="normal" />
<s:State name="edit" />
</local:states>
<s:Button includeIn="edit" label="Extra edit button" />
</local:AdvancedPanel>
Using the CustomAdvancedPanel inside an application component shows an empty panel with the "Go to edit" button. But when I click it, the "Extra edit button" becomes visible, but the "Show in edit" button inside the controlBar doesn't.
When the CustomAdvancedPanel is empty, without redeclared states and "Extra edit button" the panel works just fine.
I think it is because the State object declared in AdvancedPanel isn't the same as CustomAdvancedPanel, so the state is different, even if they have the same name. However. I can't use the states of AdvancedPanel inside CustomAdvancedPanel without (re)declare them in mxml.
Is there any way to achieve this kind of state-reuse? Or is there a better way to obtain the same result?
I suggest you to use Spark's skinning architecture to obtain your goals. Because skin states are inherited in host component you can place all the logic in OOP way. But skins will still contain duplicate code :( Anyway it is better than duplicate code of all the component.
So our AdvancedPanel will look like the following:
package
{
import flash.events.MouseEvent;
import spark.components.supportClasses.ButtonBase;
import spark.components.supportClasses.SkinnableComponent;
[SkinState("edit")]
[SkinState("normal")]
public class AdvancedPanel extends SkinnableComponent
{
[SkinPart(required="true")]
public var goToEditButton:ButtonBase;
[SkinPart(required="true")]
public var showInEditButton:ButtonBase;
private var editMode:Boolean;
override protected function getCurrentSkinState():String
{
return editMode ? "edit" : "normal";
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == goToEditButton)
goToEditButton.addEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
}
override protected function partRemoved(partName:String, instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == goToEditButton)
goToEditButton.removeEventListener(MouseEvent.CLICK, onGoToEditButtonClick);
}
private function onGoToEditButtonClick(event:MouseEvent):void
{
editMode = true;
invalidateSkinState();
}
}
}
And for CustomAdvancedPanel:
package
{
import spark.components.supportClasses.ButtonBase;
public class CustomAdvancedPanel extends AdvancedPanel
{
[SkinPart(required="true")]
public var extraEditButton:ButtonBase;
}
}
Of course you can inherit from Panel class but I made sample code more simple.
And the skins:
<?xml version="1.0" encoding="utf-8"?>
<!-- AdvancedPanelSkin.mxml -->
<s:Skin 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:Metadata>
[HostComponent("AdvancedPanel")]
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="edit" />
</s:states>
<s:Panel left="0" right="0" top="0" bottom="0">
<s:controlBarContent>
<s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
<s:Button id="goToEditButton" label="Go to edit" />
</s:controlBarContent>
</s:Panel>
</s:Skin>
And:
<?xml version="1.0" encoding="utf-8"?>
<!-- CustomAdvancedPanelSkin.mxml -->
<s:Skin 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:Metadata>[HostComponent("CustomAdvancedPanel")]</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="edit" />
</s:states>
<s:Panel left="0" right="0" top="0" bottom="0">
<s:Button includeIn="edit" label="Extra edit button" id="extraEditButton" />
<s:controlBarContent>
<s:Button id="showInEditButton" label="Show in edit" includeIn="edit" />
<s:Button id="goToEditButton" label="Go to edit" />
</s:controlBarContent>
</s:Panel>
</s:Skin>
AFAIK the component's state does not cross over to inherited components. Think about it - if that were the case (if you could inherit states) then it would make life really complicated whenever you want to extend a component; you would have to be aware of all inherited states and not step on their toes.
I reckon it's a limitation of OO programming, but not sure what exactly. I'm no Flex expert but I thought about it from an object-oriented programming point of view and here's what I think happens:
First consider that when you create an object, Flex (or any OO language) automatically creates a copy of that object AND a private copy of its parent object, which in turn creates a private copy of its parent object and so on up the entire object tree. That might sound weird but as an example of this, when you write super() in a constructor you are calling the constructor of the parent class.
Flex has what it calls "properties". This is the equivalent of what in Java would be a private member field (variable) with a public getter and setter method. When you declare
<local:states>xyz</local:states>
you are effectively saying
states = xyz
which in turn is the AS equivalent of saying
setStates(xyz)
The important part, and this is a general rule about properties, is that setStates is a public method, anyone can call this. However the states array itself is private. If you don't declare one, CustomAdvancedPanel has no states property. Neither does it have a setStates or getStates method. However as setStates/getStates are public, it inherits them from AdvancedPanel so it funcions as if it has these methods. When you call one of these methods (get or set the states array), it actually calls the method where it exists, which is in its parent object, AdvancedPanel. When AdvancedPanel executes the method the value of the states array in AdvancedPanel itself is read or set. This is why when you don't redeclare any states in CustomAdvancedPanel everything works perfectly - you think you are setting and getting the states array in CustomAdvancedPanel but in fact behind the scenes you are operating on the states array in the AdvancedPanel parent object, which is perfectly fine and good.
Now you redefine the states array in CustomAdvancedPanel - what is happening? Remember that declaring a property in Flex is like declaring a private class-level variable and public getters and setters. So you are giving CustomAdvancedPanel a private array called states and public getters and setters to get/set that array. These getters and setters will override the ones from AdvancedPanel. So now your application will interact with CustomAdvancedPanel the same way but behind the scenes you are no longer operating on the methods/variables of AdvancedPanel but rather on the ones you have declared in CustomAdvancedPanel itself. This explains why when you change the state of CustomAdvancedPanel, the part that is inherited from AdvancedPanel does not react, since its display is linked to the states array in AdvancedPanel, which still exists independently.
So why isn't the includeIn allowed in the basic example where you don't redeclare the states? I don't know. Either it's a bug, or perhaps more likely, there's a legitimate language/OO reason why it could never work.
It's possible that my explanation is not totally accurate. That's as far as I understand things. I myself don't know why that would really happen considering the Button in question is part of the superclass. A couple of interesting tests would be:
move the click handler into an actual public method instead of inline.
add super.currentState='edit' to the click handler.
If you want to learn more about all this inheritance stuff, write some simple classes in ActionScript or Flex with one class inheriting another, and run various function calls to see what happens.
"Or is there a better way to obtain the same result?"
Since you asked, and because you didn't make a clear case as to the need for the extra CustomAdvancedPanel component, putting the "Extra edit button" in the AdvancedPanel component is the simplest solution.
<!-- AdvancedPanel.mxml -->
<s:Panel>
<s:states>
<s:State name="normal"/>
<s:State name="edit"/>
</s:states>
<s:Button includeIn="edit" label="Extra edit button"/>
<s:controlBarContent>
<s:Button
includeIn="edit"
label="Show in edit"/>
<s:Button
label="Go to edit"
click="{currentState='edit'}"/>
</s:controlBarContent>
</s:Panel>
Assaf Lavie is right, it would be very confusing, if a custom component had its parent's states. i'd say consider using skins:
Flex 4 Component States vs. Skin
States
Advanced Flex 4 Skinning Techniques
Of course the politically correct way is to use skins. However, for those who really just want to brute force state inheritance for MXML classes here is a work around that I have found.
For this method to work, the extending MXML class should declare exactly the same states of the base MXML class, no more and no fewer, all with identical names.
Then in the extending class insert the following method:
override public function set states(value:Array):void
{
if(super.states == null || super.states.length == 0)
{
super.states = value;
for each (var state:State in value)
{
state.name = "_"+state.name;
}
}
else
{
for each (var state:State in value)
{
state.basedOn = "_"+state.name;
super.states.push(state);
}
}
}
This works because as the component is created the states variable is set twice, once by the base class, and once by the extending class. This workaround just combines them together.

Using a composite MXML component from ActionScript

I'm trying componentize one of the pieces of UI in an AIR application that I'm developing in Flex. In this example, I want to display file information on a single line (which has an icon, some text/link and the size).
My code looks like this (component is called FileDisplay):
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public function set iconType(source:String):void {
this.ficon.source = source;
}
public function set fileName(name:String):void {
this.fname.htmlText = name;
}
public function set fileSize(size:String):void {
this.fsize.text = size;
}
]]>
</mx:Script>
<mx:Image id="ficon" />
<mx:Label id="fname" left="20" right="30" text="Filename" />
<mx:Label id="fsize" right="0" text="0 K" />
</mx:Canvas>
When I'm using this component in my main application, the actionscript looks like:
for each (var file:XML in result.files) {
var fd:FileDisplay = new FileDisplay();
fd.fileName = ''+file.name+'';
fd.iconType = getFileTypeIcon(file.name);
fd.fileSize = getFileSizeString(file.size);
this.file_list.addChild(fd);
}
However, when I do this, I get an error: Error #1009: Cannot access a property or method of a null object reference. This is because the child components of the FileDisplay are null (or at least they show up that way in the debugger).
Does anyone know if there's a way around this? Am I supposed to be waiting for events indicating the child components were created? Is there a more common pattern that solves this problem?
For now I can manually do everything in ActionScript in my main app (create a Canvas and add children to it) but I would appreciate any insight on how to separate the code more cleanly.
Bindable to the rescue:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
[Bindable]
public var iconType:String;
[Bindable]
public var fileName:String = "Filename";
[Bindable]
public var fileSize:String = "0 K";
]]>
</mx:Script>
<mx:Image id="ficon" source="{iconType}"/>
<mx:Label id="fname" left="20" right="30" text="{fileName}" />
<mx:Label id="fsize" right="0" text="{fileSize}" />
</mx:Canvas>
the values will be automatically updated when the components are created.
The subcomponents haven't been loaded yet.
Read this: http://livedocs.adobe.com/flex/3/html/help.html?content=ascomponents_advanced_2.html#203434.
Then, when like me, you don't understand it (and it's not reliable), listen for the FlexEvent.CREATION_COMPLETE within FileDisplay, and apply your child component properties there.
Or better yet, create the three children programmatically in the "createChildren" function, and apply the settings there.
Both of these methods assume that you're setting filename, icontype, and filesize as local members before applying them to the children components, which you should be doing regardless.
What is the parent component that holds the FileDisplay component? If you're sure that the error is coming from the fact that the child components of FileDisplay aren't being instantiated then you might want to look at the creationPolicy attribute and make sure it's set to ContainerCreationPolicy.ALL on that parent component.
=Ryan
In addition to setting the CreationPolicy to all, you need to add the DisplayObject to the stage via addChild. The children of FileDisplay are not created until you add it is added to the stage. So do:
for each (var file:XML in result.files) {
var fd:FileDisplay = new FileDisplay();
this.file_list.addChild(fd);
fd.fileName = ''+file.name+'';
fd.iconType = getFileTypeIcon(file.name);
fd.fileSize = getFileSizeString(file.size);
}

Resources