views:

222

answers:

5

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 has 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?

A: 

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?

invertedSpear
The code executes fine and stack trace shows that the variables are defined. As I say, the problem is that the function setting the variable finishes AFTER the getter executes, so the getter comes up null.
Steve
+1  A: 

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.

phwd
Thanks for the link and info, but the problem isn't the setter. The variable sets fine, but the get function is executing before the setter finishes. My thought would be to put something like the async token in the getter, but there's no event trigger on it. I may need to use a different design pattern to wait for the setter to set before executing the getter...
Steve
My bad. :( ... You can try dispatching an event change from the setter dispatchEvent( new Event(Event. CHANGE) ); and add an event listener to your model before the load like model.addEventListener(Event.CHANGE, set) where set is the changing of the boolean and setting the this._xml
phwd
A: 

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.

J E Bailey
Could you provide a bit of code to illustrate this? I've been trying to find examples of this concept, but can't figure out how to get my model class to notify the main app that it's ready.
Steve
A: 

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);
}
Gus
Thanks for the help, Gus, but this is where I'm getting stuck. I'm not sure how to create a custom event inside the model class/loadXML function and have it pass something back to the main app. I've already got a ResultEvent that triggers the setXLM function, but I can't figure out how to get it to return something ot the main app. Do you have some code that does this?
Steve
Follow the link to see how the dispatching of custom events works. http://livedocs.adobe.com/flex/3/html/help.html?content=createevents_3.htmlthere are three basic steps:1.) Create your custom event (optional, you could re-dispath the ResultEvent).2.) set the metadata for the event. (will allow the MXML compiler recognize the event)3.) dispatch the eventdont forget to override EventDistpatcherhere are other links that can help you:http://bit.ly/9WlEWAhttp://livedocs.adobe.com/flex/3/langref/flash/events/EventDispatcher.html#includeExamplesSummary
Gus
The line:var modelEvent:ModelEvent = new ModelEvent(ModelEvent.LOADED); is causing error: "Call to a possibly undefined method..."This feels really close and I've been trying to tweak the code to get it working...any idea why this wouldn't be recognized?Thanks!
Steve
You have to create a ModelEvent class which extends Event, there you define a static const called LOADED (for instance) that identifies the load of the XML.check the link from the livedocs I provided earlier about the custom events
Gus
A: 

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!

Steve