Why doesn't mxml support component constructors? - apache-flex

Why doesn't the Flex framework's mxml language support a constructor for components or accept constructor arguments for components? It's as far as I know not possible to declare an ActionScript object in mxml if it takes constructor arguments. I'm curious about the reason. Is it a design choice by Adobe or related to how declarative languages work? For example, why not allow:
<myNameSpace:MyComponent constructor="{argArray}"/>

You can read IMXMLObject help API for more information about your question. They're not telling exactly why an mxml doesn't support constructors, but it says that you must control your mxml component throught its lifecycle events: preinitialize, initialize and creationComplete.
I suppose that's a design decision, considering that an mxml gets translated directly to AS3 code (you can compile your application adding keep-generated-actionscript=true and see what it produces).

Even if a class is defined in MXML, it is possible to implement a constructor via instantiating an instance variable as follows. This will get called before various events like "preinitialize" or "creationComplete" are dispatched.
<myNameSpace:MyComponent>
<fx:Script>
<![CDATA[
private var ignored:* = myInstanceConstructor();
private function myInstanceConstructor():* {
// Do something - called once per instance
return null;
}
]]>
</fx:Script>
</myNameSpace:MyComponent>
Moreover, class variables can be initialized in a similar way as follows.
<myNameSpace:MyComponent>
<fx:Script>
<![CDATA[
private static var ignored:* = myClassConstructor();
private static function myClassConstructor():* {
// Do something - called once per class
return null;
}
]]>
</fx:Script>
</myNameSpace:MyComponent>

Related

Dispatching and handling an event from an ANE (Air Native Extension) to a flex mobile application

The explanation of my problem requires some background info making the question a little long, so bear with me.
An ANE (Air Native Extension) consists of 3 parts, the native code that allows you to use device specific features (I'm using android), the actionscript library that accesses those native code functions and makes them available for the Flex mobile application, and the Flex mobile application.
For my ANE, I'm getting internet info in the onReceive() method that I extend from broadcast receiver. Now that method doesn't return anything, so when it's done I store it in a global variable and dispatch an event saying that the info is ready.
The actionscript interface listens for this event and when it receives it, it updates a global variable and then also dispatches an event telling the flex mobile app that the info is ready.
So when the flex mobile app learns that the info it needs is ready it goes in and gets it.
Here's an example of how I coded it if the explanation wasn't clear enough:
NATIVE CODE:
public MyClass(AnotherClass v,FREContext c){
this.v = v;
this.c = c; //Will use c to dispatch event
}
onReceive(){
....code....
String x = "internet info"
AnotherClass.setGlobalVar(x);
c.dispatchStatusEventAsync("status", "internetInfoReady");
}
ACTIONSCRIPT INTERFACE CODE:
private var context:ExtensionContext;
var info:String;
public function Interface(){
context = ExtensionContext.createExtensionContext("id",null);
context.addEventListener(StatusEvent.STATUS,onStatus);
}
public function scan():void{
//calls function that runs asynchronously to get internet info
}
public function onStatus(event:StatusEvent):void{
if((event.level == "status") && (event.code="internetInfoReady")){
info=String(context.all("getInfo")); //function that retrieves value x in AnotherClass and returns it
dispatchEvent(new Event("internetInfoReady"));
}
}
public function getInfo():String{
return info;
}
FLEX MOBILE APP CODE:
<s:View ....
<fx:Script>
<![CDATA[
var a:Interface = new Interface();
protected function getInfo(event:MouseEvent):void{
a.scan();
test.addEventListener("internetInfoReady",onGetInfo);
}
protected function onGetInfo(evt:Event):void{
var info:String = "";
if(evt.type == "internetInfoReady"){
info = a.getInfo();
a.toast(info); //function that calls android built in toast commands
}
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Button left="251" right="250" top="40" height="43" label="Get Info" click="getInfo(event)"
fontFamily="Arial" horizontalCenter="0"/>
The app isn't running on my device and I just want to make sure that I'm dispatching the events right. I created an android app that uses the native code already written to display the information gotten on the screen so I can make sure that part works, and it does so I know that's not the problem.
Your as3 interface class must extends EventDispatcher.
I notices one strange thing in case custom event in extension project
dispatchEvent(new CustomEvent(CustomEvent.CONSTANT, someData));
does not fire event, but
var ce:CustomEvent = new CustomEvent(CustomEvent.CONSTANT);
ce.data = someData;
dispatchEvent(ce);
works fine

Having trouble with binding

I'm not sure if I'm misunderstanding the binding in Flex. I'm using Cairngorm framework. I have the following component with code like:
[Bindable]
var _model:LalModelLocator = LalModelLocator.getInstance();
....
<s:DataGroup dataProvider="{_model.friendsSearchResults}"
includeIn="find"
itemRenderer="com.lal.renderers.SingleFriendDisplayRenderer">
<s:layout>
<s:TileLayout orientation="columns" requestedColumnCount="2" />
</s:layout> </s:DataGroup>
in the model locator:
[Bindable]
public var friendsSearchResults:ArrayCollection = new ArrayCollection();
Inside the item renderer there is a button that calls a command and inside the command results there is a line like this:
model.friendsSearchResults = friendsSearchResults;
Putting break points and stepping through the code I confirmed that this like gets called and the friendsSearchResults gets updated.
To my understanding if I update a bindable variable it should automatically re-render the s:DataGroup which has a dataProvider of that variable.
There's nothing obviously wrong in the code sample. It should work so I think there's a problem elsewhere.
I would recommend setting a breakpoint where the dataProvider is assigned and also where model.friendsSearchResults is assigned. Make sure they're both pointing to the same object instance. Then step through the property assignment and corresponding event.
To make debugging easier you can switch to using a named event instead of the default. With a named event, only event listeners interested in your particular property are triggered instead of any listeners listening for any property change. This is easier to debug and will run faster. For example, change:
[Bindable]
public var results:ArrayCollection;
to
[Bindable("resultsChanged")]
private var _results:ArrayCollection;
public function get results():ArrayCollection {
return _results;
}
public function set results(value:ArrayCollection):Void {
_results = value;
dispatchEvent(new Event("resultsChanged"));
}
Another thing to keep in mind is that bindings hide certain errors like null reference exceptions. They assume the value simply isn't available yet and suppress the error. Stepping through the assignment and related bindings will help find a problem like this.

Injecting an object created by OjbectBuilder as a property to view

I have a PresentationModel AS class that holds all the values used in SomeView.mxml. The entire class for the model is bindable, and the model property in the view is also bindable. However, I am unable to inject the model into the view using the PropertyInjector tag:
- INFO: Data binding will not be able to detect assignments to model
Would someone familier with Flex data binding and Mate give me a hand? Thanks a lot!
MainEventMap.mxml
<EventHandlers type="{FlexEvent.INITIALIZE}">
<ObjectBuilder generator="{PresentationModel}" registerTarget="true">
<Properties dispatcher="{scope.dispatcher}"/>
</ObjectBuilder>
</EventHandlers>
<Injectors target="{SomeView}" debug="true">
<PropertyInjector targetKey="model" source="{PresentationModel}" />
</Injectors>
Snippet from PresentationModel.as
[Bindable]
public class PresentationModel extends EventDispatcher
{
public var dispatcher:IEventDispatcher;
//.....other variables and functions
}
Snippet from SomeView.mxml
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="518" height="562" >
<mx:Script>
<![CDATA[
//...... all the imports
[Bindable]
public var model:OSGiBrokerConsoleModel;
// ......other variables and functions
]]>
</mx:Script>
// ..... actual view components
</mx:Canvas>
You can safely ignore that information message.
That message is usually shown when you have a PropetyInjector with source and source key, where the property defined by "sourceKey" is not bindable, so we want to make sure you know that the current value of that property will be the only one the target will ever get (when the property is not bindable, the value is copied and no binding is established). That may or may not be what you want.
In this case, there is no sourceKey, because you don't want to bind to any specific property of the source. Instead, you want to pass the whole PM to the view. Because of that, you don't want to establish a binding, just send the value to the view once.
In the cases where there is no sourceKey or when you are simply sending a one-off value (ie: when you are sending a constant), the message can be ignored.
You cannot bind to a class. Making a class bindable means all members of that class will be bindable, but not the definition itself.
You should create a member function(getter/setter) for the presentation model that returns the data you want to use as the source. Then you also need to create an instance of PresentationModel that you can use for the binding. So rather than binding to PresentationModel.data, you'd bind to myPM.data.

flex doesn't seem to bind with custom actionscript object

I have a custom actionscript object defined as bindable with a number of public properties.
[Bindable]
public class MyObject extends Object {
public var mobileNumber:String;
...
In my mxml I have:
<mx:Script><![CDATA[
import mx.binding.utils.BindingUtils;
import org.test.MyObject;
[Bindable]
private var obj: MyObject = new MyObject();
]]></mx:Script>
<mx:Label text="Mobile Number" id="mobileNumberLabel"/>
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
<mx:LinkButton label="Load" id="loadButton" enabled="true" click="obj = obj.load();"/>
<mx:LinkButton label="Save" id="saveButton" enabled="true" click="obj.write();"/>
My issue is that when I enter a new value in the field for mobile number and then click the save button, the value typed is not logged out... i.e.:
public function write():void {
var bytes:ByteArray = new ByteArray();
trace("write - mobile:" + this.mobileNumber);
bytes.writeObject(this);
EncryptedLocalStore.setItem(KEY, bytes);
}
I also tried adding in:
private function init():void {
BindingUtils.bindProperty(mobileNumberText, "text", obj, "mobileNumber");
}
but no luck with that either.
I'm probably missing something simple here, but not sure what it is. Hope you can help, thanks.
tst's answer is correct - bindings are one-way. I'm guessing you already knew that though, since you tried to setup the reverse binding in your init() method.
However, there's two problems with your init() method.
First, it's not clear where you put that init() method, or what calls it.
Second, you got the method parameters backwards.
What I typically do in situations like this is either use the mxml tag as the first responder suggested, or if I'm in AS3 code, I do something like this:
private function onCreationComplete(event:Event):void
{
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumberText, ["text"]);
}
Note a couple of points here:
1/ BindingUtils.bindProperty() is "left hand side = right hand side". Thus, it's kinda like writing
obj.mobileNumber = mobileNumberText.text;
Or, closer to what is "actually going on" inside the binding classes:
obj["mobileNumber"] = mobileNumberText["text"];
2/ BindingUtils.bindProperty() actually wants an array as the last param. This is so that you can do "chained properties" logically like:
obj.mobileNumber = mobileNumbersGrid.selectedItem.text;
which would be
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumbersGrid,
["selectedItem", "text"]);
3/ Best practice tip: if you're binding a property chain whose initial member is a property of yourself (this), then write it as:
BindingUtils.bindProperty(obj, "mobileNumber", this,
["mobileNumbersGrid", "selectedItem", "text"]);
This neatly handles the case where your "right hand side object" this.mobileNumbersGrid instance itself is replaced with a new instance object.
4/ If you ever reassign obj ("the left hand side"), you need to create a new binding to the new obj instance. Typically, you do this by turning the local obj property into a getter/setter pair like this:
public function get obj():MyObject
{
return _obj;
}
private var _obj:MyObject = new MyObject();
public function set obj(value:MyObject):void
{
_obj = value;
BindingUtils.bindProperty(_obj, "mobileNumber", mobileNumberText, ["text"]);
}
(Note: to be really careful, you'd stash the returned value from BindingUtils.bindProperty() which is a ChangeWatcher instance, and you'd unwatch() to prevent the old object from receiving property changes)
Hope this helps. Bindings are among the most powerful tools in the Flex framework, but can cause a lot of headaches when used poorly. In particular, be aware of memory leaks and "unexpected updates" when bindings are left "hanging around".
This code:
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
makes a one-directional binding between obj.mobileNumber and mobileNumberText.text. What you need is an additional reverse binding - add the following line to your code and it will work:
<mx:Binding source="mobileNumberText.text" destination="obj.mobileNumber"/>
Did you try implementing IEventDispatcher, or extending EventDispatcher?
Bindings work through dispatching of PropertyChangeEvents, so maybe this is the problem?
also, I am not sure, whether bindings work on variables, or whether they require properties (getters/setters).
You should try compiling with -keep-generated-actionscript and see if the resulting code makes any sense, i.e. dispatches any events, if the variable is accessed ...

Mxml and as3 confusion [simple]

I was wondering i can call an as3 function defined in script from mxml code just like this:
<mx:Line x="translateX(xmin);" .. >
<mx:Script>
<![CDATA[
// do some basic math
private function translate ...
If not possible do i have to convert everything to as3 ?
Thanks
You can but a straight-up function call like that needs to go into an event attribute in MXML, i.e. "when this event is dispatched, invoke this function." The classic example being:
<mx:Button label="Hello" click="myFunction()"/>
You can use a function as you have illustrated above provided that it's in a binding expression and the arguments passed to the function are bindable:
<mx:Line x="{positionLine(xmin)}"/>
// defined somewhere in a mx:Script block
[Bindable] private var xmin : Number;

Resources