Flex 4: Getter getting before setter sets
I've created an AS class to use as a data model, shown here:
package
{
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
public class Model
{
private var xmlService:HTTPService;
private var _xml:XML;
private var xmlChanged:Boolean = false;
public function Model()
{
}
public function loadXML(url:String):void
{
xmlService = new HTTPService();
if (!url)
xmlService.url = "DATAPOINTS.xml";
else
xmlService.url = url;
xmlService.resultFormat = "e4x";
xmlService.addEventListener(ResultEvent.RESULT, setXML);
xmlService.addEventListener(FaultEvent.FAULT, faultXML);
xmlService.send();
}
private function setXML(event:ResultEvent):void
{
xmlChanged = true;
this._xml = event.result as XML;
}
private function faultXML(event:FaultEvent):void
{
Alert.show("RAF data could not be loaded.");
}
public function get xml():XML
{
return _xml;
}
}
}
And in my main application, I'm initiating the app and calling the loadXML function to get the XML:
<fx:Script>
<![CDATA[
import mx.containers.Form;
import mx.containers.FormItem;
import mx.containers.VBox;
import mx.controls.Alert;
import mx.controls.Button;
import mx.controls.Label;
import mx.controls.Text;
import mx.controls.TextInput;
import spark.components.NavigatorContent;
private function init():void
{
var model:Model = new Model();
model.loadXML(null);
//the following line executes before model.loadXML has finished...
var xml:XML = model.xml;
}
]]>
</fx:Script>
The trouble I'm having is that the getter function is running before loadXML has finished, so the XML varible in my main app comes up undefined in stack traces. Specifically, the loadXML function called ResultEvent.RESULT, then jumping to setXML, etc...the code in the main app continues to execute while loadXML waits for a result, so the getter in the main app (var xml:XML = model.xml;) executes before the variable h开发者_JS百科as been defined by setXML.
How do I put a condition in here somewhere that tells the getter to wait until the loadXML() function has finished before running?
This should work as I said in the comments you should use the
Asynchronous Completion Token Design Pattern Flex/AS does not to synchronous calls.
public function loadXML(url:String):void
{
xmlService = new HTTPService();
if (!url)
xmlService.url = "DATAPOINTS.xml";
else
xmlService.url = url;
xmlService.resultFormat = "e4x";
xmlService.addEventListener(ResultEvent.RESULT, setXML);
xmlService.addEventListener(FaultEvent.FAULT, faultXML);
var xmlCall:Object = xmlService.send();
xmlCall.name = "SET";
}
private function setXML(event:ResultEvent):void
{
var xmlCall:Object = event.token; // Asynchronous Completion Token
if(xmlCall.name == "SET"){
xmlChanged = true;
this._xml = event.result as XML;
}
else {
// not ready to set
}
}
You can read up the design pattern here.
Your model should extend EventDispatcher and (with custom events) dispatch an event indicating that the model has been successfully loaded. This is needed because Flex is Asynchronous...
Then in the Application after create the model just add the event listener to the load event, and in there is where you can start using the model vars
Don't forget to dispatch and listen to any error events!
HTH Gus
Something like this...
in the main app:
var model:Model = new Model();
// registering the listener
model.addEventListener(ModelEvent.LOADED, model_loadedHandler);
model.loadXML(null);
then the listener
private function model_loadedHandler (event:ModelEvent):void
{
var xml:XML = model.xml;
}
and finally, in the model:
private function setXML(event:ResultEvent):void
{
xmlChanged = true;
this._xml = event.result as XML;
var modelEvent:ModelEvent = new ModelEvent(ModelEvent.LOADED);
// you can add other info to the event like the XML
dispatch(modelEvent);
}
This may just be a misprint in what you pasted here but
private function setXML(event:ResultEvent):void
is missing a space, making a setXML function instead of settign an XML variable... Is that causeing your issue?
This would be working as designed. Since the xml service is an Asynchronous request it's firing the request off and then continuing to evaluate steps. In this case going on to call the getter method.
So the main thing to look at is that you can't set the xml directly. You have to have the xml object set after the result is returned. You can do that by adding an event listener to your model so that once the service is returned your main package is notified as well, or by Binding the xml value between your main package and the models xml.
Thanks for all the help on this, especially Gus for spoon-feeding me that great code and the links. Here is the final code I used to execute this:
Main app:
<fx:Script>
<![CDATA[
import flash.events.Event;
import mx.containers.Form;
import mx.containers.FormItem;
import mx.containers.VBox;
import mx.controls.Alert;
import mx.controls.Button;
import mx.controls.Label;
import mx.controls.Text;
import mx.controls.TextInput;
import spark.components.NavigatorContent;
private var model:Model = new Model();
private function init():void
{
model.addEventListener(Event.COMPLETE, model_loadedHandler);
model.loadXML(null);
}
private function model_loadedHandler (e:Event):void
{
var xml:XML = model.xml;
var sectorList:XMLList = xml.SECTOR;
trace(sectorList);
}
}
]]>
</fx:Script>
And the Model.as:
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import mx.controls.Alert;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
public class Model extends EventDispatcher
{
private var xmlService:HTTPService;
private var _xml:XML;
public function loadXML(url:String):void
{
xmlService = new HTTPService();
if (!url)
xmlService.url = "DATAPOINTS.xml";
else
xmlService.url = url;
xmlService.resultFormat = "e4x";
xmlService.addEventListener(ResultEvent.RESULT, setXML);
xmlService.addEventListener(FaultEvent.FAULT, faultXML);
xmlService.send();
}
private function setXML(event:ResultEvent):void
{
this._xml = event.result as XML;
var modelEvent:Event = new Event(Event.COMPLETE);
//var modelEvent:ModelEvent = new ModelEvent(ModelEvent.LOADED);
dispatchEvent(modelEvent);
//this._xml = event.result as XML;
}
private function faultXML(event:FaultEvent):void
{
Alert.show("RAF data could not be loaded.");
}
public function get xml():XML
{
return _xml;
}
}
}
I ended up using a standard event class (I think, not entirely sure why this works yet), and now the code is running perfectly. Any further thoughts on this would be appreciated, but it's running!
精彩评论