Disable databinding in MXML - apache-flex

When the Flex SDK converts MXML to actionscript it generates a lot of databinding code. Sometimes, however, I don't want to bind a variable, for example if I know the variable will not change.
I can't seem to find a work around in Flex to disable the autogenerated databinding.
Also, I was hoping this might also help with some of the runtime warnings thrown by databinding.
To get around them, I sometimes use the following, which only throws syntax warnings (and don't appear in my console at runtime).
Syntax warning:
Data binding will not be able to detect changes when using square bracket operator. For Array, please use ArrayCollection.getItemAt() instead.

The following tag will tell Flex SDK that variable do not really change and remove "Unable to bind ..." warnings:
[Bindable("__NoChangeEvent__")]
private var model:MyModel = MyModel.instance;
Next, move array[i]-like expressions to a separate function in order to remove warnings. If you had this:
<mx:Button label="{array[i]}"/>
Then create a function:
private function buttonLabel(i):String
{
return array[i];
}
And the MXML:
<mx:Button label="{buttonLabel(i)}"/>
P.S: If button label changes in runtime then you can add [Bindable(...)] metatags to the function:
[Bindable("stringsChange")]
private function buttonLabel(i):String
{
return array[i];
}
dispatchEvent(new Event("stringsChange"));

Related

Flex: NPE and Proper way to access component properties before creation complete

I think I'm missing something important about flex sdk component lifecycle, but cannot sort it out, although read a lot of tutorials. Could you please share your experience on how do you operate with flex visual object's properties and how do you avoid NPE when accessing them before component creation complete.
Let's say we have a simple component MyTitleWindow.mxml:
<s:TitleWindow>
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Another component got data from a remote object, and wants to apply the data into title window's datagrid and show it via PopUpManager:
private function handleDataReceived(data : ArrayCollection) : void {
var myTitleWindow : TitleWindow = new MyTitleWindow();
PopUpManager.addPopUp(myTitleWindow);
myTitleWindow.myDataGrid.dataProvider = data;
}
Ofcourse, the line myTitleWindow.myDataGrid.dataProvider = data will throw an NPE because we're trying to access myDataGrid that haven't been rendered yet.
Currently I can see only 2 options how to avoid NPE:
Create a setter for data in titleWindow, put the data into some
cache. Listen to creationComplete event, in it's handler apply data
from the cache to the datagrid. This approach works fine, but I'm
tired of adding this safe-guards across the application.
Make a
bindable property - works for me only with simple datatypes
(numbers, strings...)
Is there anything I'm missing in the area of using flex validation/invalidation cycle that could help to avoid excessive code?
The problem you are having and Crusader is reporting is because in Flex components are initialized lazy. This is generally a good thing but in your case it is what's causing your problems.
In general I wouldn't suggest to set the dataProvider on a view component from outside the component as you have no way of knowing if all is setup and ready to use.
What I usually do. In simple cases I simply add a public propberty which I make [Bindable]. The (i think) cleaner way would be to create a setter (and getter) and so save the dataProvider in a local variable (in your case probably ArrayCollection). In the setter I usually check if the "myDataGrid" exists and if it exists, to additionally set the dataProvider property. I would then add a CreationComplete callback in my component and in that I would also set the dataProvider.
So when setting the dataProvider before the component is finished initializing, the value would simply be saved in the local variable and as soon as it is finished setting up the dataProvider is automatically set. If the component is allready setup (you are changing the dataProvider) the setter would automatically update the dataProvider of "myDataGrid"
<s:TitleWindow creationComplete="onCreationComplete(event)">
...
private var myDataProvider:ArrayCollection;
private function onCreationComplete(event:FlexEvent):void {
myDataGrid.dataProvider = myDataProvider;
}
public function set myDdataProvider(myDataProvider:ArrayCollection):void {
myDataProvider = myDataProvider;
// Only update the dataProvider if the grid is available.
if(myDataGrid) {
myDataGrid.dataProvider = myDataProvider;
}
}
....
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Yeah, this can be an annoyance, and it's not just with popups. It can also be a pain when using a ViewStack components with a default creationpolicy, which I tend to do fairly often.
I might get flamed for this, but I usually just use bindings. I'm not sure what you mean by "simple datatypes" - it works fine with custom types too. You'd have to provide an example.
One thing you could do (and I'll probably get flamed for this :p ) is create your popup component instance early on and re-use it rather than creating a new one each time.
No, I don't think you're "missing something" in the component lifecycle.
I always try to invert the responsibility for setting a dataProvider and prefer to have components observe a [Bindable] collection.
In simple examples such as yours I avoid giving my components an id. This prevents me from breaking encapsulation by referring to them externally.
<s:TitleWindow>
<s:DataGrid dataProvider="{data}" />
</s:TitleWindow>
The consumers of your MyTitleWindow component should not know that it has a DataGrid with id 'myDataGrid', or be required to set the dataProvider property of 'myDataGrid'.
Considering components declared in MXML require a no-arguement constructor (and that we're unable to declare multiple constructors) - one approach that has worked well for me in the past is to offer a static 'newInstance' method. I give this method a relevant name based on the domain I'm working in, and also any required parameters.
public static function withData(data : ArrayCollection) : MyTitleWindow
{
var myTitleWindow : MyTitleWindow = new MyTitleWindow();
myTitleWindow.data = data;
return myTitleWindow;
}
This clearly communicates the 'contract' of my component to any and all consumers. (Obviously things become clearer with more relevant naming).
private function handleDataReceived(data : ArrayCollection) : void
{
PopUpManager.addPopUp(MyTitleWindow.withData(data));
}

Passing data and calling functions between host component and renderers in Flex

Is there any better way for passing data between component and its renderers (also calling methods)?
I am using dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or owner.dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or bubbling (depends on situation). And then listening in that component for my event.
And for passing data to renderer, I am using:
(myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data); or modifying dataProvider but then I have to callLater and call method inside renderer anyways.
This sucks, I have to callLater all the time for to wait for renderers set data function to execute, otherwise renderer is not recycled yet and is null.
Is there any more efficient, prettier way to transfer data and call functions between renderer and component?
P.S. And yes I have pass data to renderer. My renderer handles quite complicated logic.
I've resolved a problem similar to yours in this way:
var goalPropDLRenderer:ClassFactory =
new ClassFactory(GoalPropagationDataListRenderer);
goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode };
list.itemRenderer = goalPropDLRenderer;
Where: "list" is a list object for witch I've to set an item renderer; instead of setting the item renderer inline (on the "list" declaration via mxml), I've used this syntax. This code is called on the creationComplete of the "list" father (I think there are better places) In this way it's possible to pass "properties"/"values" to the renderer.
On GoalPropagationDataListRenderer I've:
[Bindable]
public var isPropagationMode : Boolean;
that gets populated by goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode }; so inside the renderer I can access the value of isPropagationMode without problems.
I hope it could help.
There are some ways, depends on what exactly you want to do (if the data are dependent to every row, or global in datagrid), but personally, I would avoid callLater and this you wrote: (myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data)
you can use bubbling event, then catch it in datagird (or whenever), and then get renderer through myRenderer(event.target), not needed to use getElementAt!, but questin is when would you dispatch the event (I dont know the details)
if you use global (in datagrid meaning) logic:
I would make some common object (called logic) which will be set in datagrid and renderers (separating logic from view is good practice anyway in my opinion)
then I would use the solution what Claudio wrote (setting properties to itemRenderer ClassFactory) to set the logic to renderer
or you can use listData object (but it's not as simple)
or if you write item renderer in mxml inside datagdid, you can easilly bind it using outerDocument like logic="{outerDocument.logic}"
or you can just use itemRenderer's owner property, so whenever yo want some methods, call like MyDataGrid(owner).doSomeDataGridCalculationsForData(data), not needed to create logic object if you dont want to

MXML-class initialization order

I have written a few custom components in Flex 4, and run into this issue a few times.
var myForm:MyForm = new MyForm;
myForm.SetData(data);
addElement(myForm);
Now Imagine I am calling these functions from a non-constructor function of a Panel or VGroup (or any other container). Annoyingly, during MyForm.SetData(), not all fields of myForm that are declared there are yet initialized. Such as:
<s:VGroup id="dataGroup">
If my SetData()-function wants to access dataGroup (for the reason to .addElement() the just recieved data to it), it just fails with a nullpointer exception, because dataGroup is not yet created, despite this being after the constructor. How can guarantee that the form was initialized fully?
Listening for the creationComplete event and adding your components in the handler for the event is one way to do this. This is what Sam DeHaan suggested.
Another way you can do this is to override the createChildren() function. This is the function that creates and adds all of a component's child components. Code would look something like this:
override public function createChildren():void
{
super.createChildren();
var myForm:MyForm = new MyForm;
// Note that data may be null here, best to
// override commitProperties() to set it.
myForm.SetData(data);
addElement(myForm);
}
The docs on the component lifecycle will provide a ton of detail on this topic.
Unless I'm misunderstanding your question,
You should put the code that runs into this null pointer exception in a creationComplete callback on the container that you need defined.

Use StyleManager.setStyleDeclaration to set button skins in Flex

I am trying to load an swf file which has button skins as images (In the library of the swf file i have given export properties which is 'TickMark') and set the skin of a flex button using StyleManager.setStyleDeclaration.
I am getting errors like 'Argument count mismatch on TickMark(). Expected 2, got 0.'
This is what i am trying to do:
private function init():void
{
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,styleSWFLoaded);
loader.load(new URLRequest("styles.swf"),new LoaderContext(false,ApplicationDomain.currentDomain));
}
private function createStyle(styleName:String):void
{
var style:CSSStyleDeclaration = new CSSStyleDeclaration(styleName);
var cls:Class = ApplicationDomain.currentDomain.getDefinition(ss) as Class;
style.setStyle("upSkin",cls);
}
}
StyleManager.setStyleDeclaration(".buttonme",style,true);
}
When I apply this new style 'buttonme' to a button i am getting below error:
ArgumentError: Error #1063: Argument count mismatch on TickMark(). Expected 2, got 0.
Not sure why is this happening, and strange thing is, when I embed the same swf file it works, like below:
[Embed(source="styles.swf", symbol="Tick")]
private var GraphicClass:Class;
If I use the class GraphicClass in setStyleDeclaration, it works... but basically I want it dynamically.
Or there are other easy methods to skin (image) a flex button dynamically?
You should be able to set your skin like that dynamically. It probably has to do with your TickMark class. I'm assuming when you do style.setStyle("upSkin", cls);, that cls is TickMark and it has two required constructor args: TickMark(arg1:Object, arg2:Object). Is that true? Somewhere in the setStyle method its doing new cls().
If so, just make sure there's no constructor arguments and it should work.
If not, try following the stack trace and using breakpoints in Flex Builder if you don't already, that should help pinpoint the problem.
Best,
Lance
I believe, when you embed the export-symbol in your flex-app, it would be taking care of size and perhaps just embedding the png directly.
As your symbol-class extends BitmapData, it has to be instantiated by passing required arguments in constructor. So whatever error you get, is by design and work as expected.
You can wrap png in some other type of symbols (sprite, movieclip, etc) and export. That should work fine when used in setStyle (..,..)

Flex adding event listeners with callback functions which have no arguments

fileReference.addEventListener(Event.COMPLETE, uploadCompleteHandler);
private function uploadCompleteHandler(event:Event):void {}
Above is one way to add an event listener in Actionscript.
By default the callback function needs to have an argument with
name event and type Event.
Is there a way to declare this function without any arguments :
private function uploadCompleteHandler():void {}
Edit : It's possible to add an event handler without any arguments in mxml. So one student wanted to know, why isn't it possible to do the same in actionscript?
The reason is that in mxml what you write isn't actually the handler, is what gets executed in the handler. If you compile with -keep-generated-actionscript flag (To set it in Flex Builder right click to open the project Properties, select Flex Compiler, and add -keep-generated-actionscript to the Additional compiler arguments), you can see in the generated source for your component, that the compiler created a handler for that event, and the body is composed by that you wrote in mxml.
So if you have something like:
click="doSomething();"
You can already notice that you're actually giving an instruction there, that's not a method reference you're passing like when you use addEventHandler.
Then you'll have in the generated file something like:
private function myComponent_Click(evt : MouseEvent) : void
{
doSomething();
}
And somewhere else in the same file the adding of the event listener:
this.addEventListener(MouseEvent.CLICK, myComponent_Click);
Notice the second parameter is not a function result, it's a function reference, because the parenthesizes that denote a function call are missing, and our particular function is not a getter either.
You can also specify more calls in mxml, like:
click="doSomething(); doSomethingElse();"
You can even pass the event parameter to your method:
click="doSomething(event);"
Whatever you write in the value of the mxml event (not sure it's the right term to use) will become the body of the generated handler for the actionscript event.
No, you'll get a runtime exception when the event fires if you'll try to register it as event listener.
You may use the following syntax to allow calling your handler directly without event object:
private function uploadCompleteHandler(event:Event = null):void {}

Resources