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.
Related
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
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 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 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'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);
}