i figure i'll start posting some of the responses i send to readers here for posterity. the first comes from josh, a java developer.
josh writes:
**********************
I have searched for hours and hours.. and no one seems to know the answer to this question.. I come from a java background so this is just mind boggling to me.. I have one simple function..
getData = function(xmldata) {
dReader = new DataReader();
gXML = new XML();
gXML.ignoreWhite = true;
gXML.onLoad = function(loaded){ trace(this.hasChildNodes()); };
gXML.load(xmldata);
trace(gXML.hasChildNodes());
}
rs = getData("../reports/salesreturned.oml");
of course the trace inside the onLoad function has children.. the one after gXML.load doesn't (I'm guessing because it hasn't been loaded yet.. I want to actually return a data from the getData function.. how are you supposed to get out of the gXML.onLoad portion.. It seems like once you want to parse the XML your'e pretty much stuck in a timeline of chaining functions from onLoad.. This is probably a more general question than just this.. it applies for all event handlers and how to "get out" of them.. I couldn't figure out how to assign a variable in the getData function data from inside the handler.. even if I did.. how would I return the function because the data isn't loaded before it would return.. it blows my mind how actionscript wouldn't process something like this sequentially since it doesn't have blocking functions.
colin answers:
**********************
hi josh,
coming from a multithreaded environment like java to a singlethreaded environment from flash is indeed a little disorienting. part of the key to understanding your situation is knowing that the actionscript interpreter does things automatically behind the scenes for you that you might not expect from a Java perspective.
first a few fundamentals:
1) all code in a flash documents executes according to either events or to frames.
2) code on a frame generally executes sequentially *unless* it involves an HTTP load operation, in which case the HTTP request isn't sent until the end of the frame on which the code occurs. the load request doesn't block the execution of code; it's just put off until the end of the frame, after all code has been executed.
3) when an HTTP request occurs, the results take some time to return. they arrive asynchronously, and must be handled by event handlers that may actually fire perhaps 10, 100, or even 1000 frames later (assuming you're not stopped on a frame).
4) unless you use 'var' to create a local variable, variable declarations in functions defined on frames create "timeline variables" which are available to any other code on frames in that timeline. (think of timeline variables as instance variables of the MovieClip object on whose timeline your code resides.)
now let's take a look at your original function. i'll add comments to explain what's happening behind the scenes.
===========
getData = function(xmldata) {
// this doesn't seem to be used(?)
dReader = new DataReader();
// create a new XML instance, store it in gXML, which
// is defined on the timeline of this function declaration.
// probably not what you want. this should likely be a
// local variable.
gXML = new XML();
gXML.ignoreWhite = true;
// assign the onLoad callback function. it will execute
// automatically when your data has loaded.
gXML.onLoad = function(loaded){ trace(this.hasChildNodes()); };
// now ask Flash to load the xml file. this actually doesn't occur
// right away. it occurs at the end of the current frame.
gXML.load(xmldata);
// the load hasn't started yet, so there are no child nodes.
trace(gXML.hasChildNodes());
}
// this is an impossible idiom in actionscript.
// data won't be returned until it loads.
rs = getData("../reports/salesreturned.oml");
===========
ok, now let's rewrite your function so it works more the way you want it to. note that i'd normally do what you're doing within a class. however, you seem to be using code on a timeline, so i'll stick with that convention for this example. i'll also try not to completely replace your current approach. i'll just adapt it a little to the Flash world.
here's the source for our sample XML file:
===========
<?xml version="1.0"?>
<P>Hello</P>
<P>There</P>
<P>World</P>
===========
here's the code on a frame in your .fla file:
===========
getData("doc.xml");
function getData (url) {
// "But wait!," you think, "the local variable will expire
// at the end of the function, before data loads!"
// Not true. ActionScript will keep it around for you internally
// until the load completes, even after the function exits.
var gXML = new XML();
gXML.ignoreWhite = true;
gXML.onLoad = handleLoadedData;
gXML.load(url);
}
function handleLoadedData (loaded) {
if (loaded) {
// extract what you want from the loaded data
var data1 = this.childNodes[0].firstChild.nodeValue;
var data2 = this.childNodes[1].firstChild.nodeValue;
var data3 = this.childNodes[2].firstChild.nodeValue;
// then do something with it.
// (i'm assuming you're updating a UI, but you
// could just set a timeline variable here.)
updateUI(data1, data2, data3);
} else {
trace("load error");
}
}
function updateUI (value1, value2, value3) {
// you'll use the values to update your ui,
// but we'll just display them in the Output
// panel for this example.
trace("Received values: ");
trace(value1); // Displays: Hello
trace(value2); // Displays: There
trace(value3); // Displays: World
}
===========
if you're using the Macromedia FUI components, you might also want to investigate the setDataProvider() method and databinding options. that lets you define an XML instance and wire a UI component to it. when the instance loads new values, the component updates automatically.
good luck!
colin
I have a flash game that I made. The levels of the game are loaded from an xml file (which is fine). The only problem is that I wish to post this game on a website (newgrounds.com) and it'll only accept the .swf. I can't load the xml files. How do I make the xml run independently without using the files? can I paste the files and put them in a funtion?
Posted by: Millionaire Hoy at November 2, 2003 08:04 PMNormally you'd setup a socket and call a function that would block (not return) until there is data available from the socket.. This basically causes java to take the thread that is running and put it on a "wait" until there is data. So if you had a thread that was running:
gXML.load(xmldata);
do something else here;
the do something else here; wouldn't run until the load function thread started running again.. In actionscript you can not have this functionality since there isn't all these threads to play around with..
Posted by: Josh at July 10, 2003 04:52 PMSo, the answer to the original question ('how to "get out" of them'..."your'e pretty much stuck in a timeline of chaining functions"), is yes. You finish the chain and wait for an event (or frame) to trigger another. The emphasis that http loads await the end of a frame is much appreciated.
Then, I'd like to extend the question with a current concern of mine...since AS is single threaded what happens when I spin off tasks (function calls)? I guess they get done sequentially unless they require an http load, in which case they wait for the end of the frame. Thus, there is no need to block and make into “atomic units” any routines that can be multiply accessed.
But, what if these routines are interrupt-driven? Specifically, if a routine is executing and a user action causes an interrupt triggering re-entry into and changes to the data of the same routine. What happens? Is another instance created? I suspect *not* since there was recently significant discussions about how to use the list of listeners (to address what happens if a listener is removed while in the process of broadcasting…copy the list and broadcast to the copy was the solution, as I remember it.) All of which brings me back to blocking and atomization for multiply-accessed, interrupt-driven routines. It seem to be necessary after all!
I will look forward to all comments and suggestions. And, I'd like to thank Colin for all of us learning from his tutorial comments. (I've already shown my appreciation by buying and highly recommending your books!)
hey jon,
if you're using a nested function literal, you can store that information in a local variable. e.g.,
function getData (url) {
var gXML = new XML();
var targetClip = _root;
gXML.ignoreWhite = true;
gXML.onLoad = function (loaded) {
if (loaded) {
trace(targetClip); // Displays: _level0
} else {
trace("load error");
}
};
gXML.load(url);
}
if not, you could use a global variable, which may not be best oop practice, but gets the job done.
you could also set a property on the XML object to store the movie clip.
of course, ideally you'd want an XML.addListener() method that let you handle the load completion in another object that had the reference to the clip you want. i suppose you could subclass XML and override onData to do that.
Posted by: colin at July 9, 2003 10:29 AMwhat's more annoying is having to do something like this inside your onLoad handlers:
//get reference to timeline
//"this" won't work because it's the xml object
var context = eval(_target)
// ...snip...
context.component.setData(data1,data2,data3)
i can't count the number of times my xml parsing functions have "failed" because I wasn't properly referencing my timeline.
I wonder what java would do at this point:
gXML.load(xmldata); // <- does java go to sleep ?
trace(gXML.hasChildNodes());
Does it go to sleep ?
Posted by: bokel at July 9, 2003 04:29 AM