Archive for the 'Actionscript' Category
More Guttershark Updates
Recently I put up some updates to guttershark - they sucked, so I’ve got another update. I was getting tired of having to include 90K for any swf that uses GS. So I went through and decoupled quite a bit.
Primarily it was all the singletons that were included. Originally, when I wrote the first version of GS I used all the singletons for performance and speed, but for how often some of those utilities are used it really doesn’t matter. And if you use FDT or Flex Builder, importing everything you need will be auto-completed anyways. So who cares!
The first version of GS included a couple classes called CoreSprite, and CoreClip - they included properties so that you’d always have access to a bunch of stuff, like a Model for instance. Instead of that, your class should create it’s own model, save it, and import it in any class that uses it.
You can still create your own base classes that contain properties you use all the time, but I’m not going to tell you what you need and what you don’t need; this is one of the reasons why GS would include everything if you subclassed either CoreSprite or CoreClip. So for any project you have, write your own base classes that included references to the things you need; that’s what I do.
Here’s a contrived example.
class Main { var model:Model; function Main() { model = new Model(); Model.set("main",model); } } class AnotherClass { private var model:Model; public function AnotherClass { model = Model.get("main"); } }
There are a number other classes that have this type of functionality on it. You can find them in the docs. Anyway, I’m much happier with this. Generally a shell swf will end up being around 35K.
There are a crap ton of examples in the repository. Make sure to check those out, you might see how awesome GS is and how much time it’ll save you.
If you wrote anything with GS in V1 or V2, you won’t be able to swap it out for GS3 without making some updates. The most notable things I changed:
- Took out CoreClip and CoreSprite. There’s GSClip or GSSprite now, but only fixes null stage references.
- Most utility classes have static methods on them instead of singletons.
- AssetManager is static
- Re-ordered method parameters on the LayoutManager to use …rest style parameters
- There’s a new DocumentController class which is simpler.
- Added a new TextAttributes class (gs.utils.TextAttributes). And added new model functionality to simplify setting text attributes on text fields. Like stylesheets, textformats, etc. Now it’s a one liner to set text attributes.
Additionally, I’ve updated the repositories on my gitweb page. The guttershark repo is the latest version, and I’ve updated the docs. You won’t need to look for gs2 docs or the gs2 repo anymore.
1 commentGuttershark Service Manager: SOAP
Ok folks, here’s how you can hook up soap with guttershark.
Here’s a main class: (there are a couple things I explain next)
package { public class Main extends Sprite { private var sm:ServiceManager; public function Main() { sm=ServiceManager.gi(); sm.createSOAPService("myWSDL","http://www.example.com/wsdl",3,5000); callSoap(); } public function callSoap() { sm.myWSDL.someMethod({ params:{someParameter:"hello world"}, resultHandlerClass:MySoapResultHandler, onResult:onSoapResult, onFault:onSoapFault, onFirstCall:onFirstCall, onMethodNotAvailable:onMethodNotAvailable, }); } public function onFirstCall():void { trace("trying soap service"); showLoader(); } public function onSoapFault(sf:SoapFault):void { trace("fault"); } public function onSoapResult(sr:SoapResult):void { trace("result"); trace(sr.raw); trace(sr.handler); //instance of resultHandlerClass. } public function onMethodNotAvailable():void { trace("method was not available on wsdl"); } } }
When you create a soap service (createSOAPService), the service manager downloads your WSDL and parses it out. It stores some information that you can get at runtime. For example, if you wanted to list out the supported methods on this wsdl:
sm.myWSDL.listMethods();
That’s generally all that you need, and that’s only needed in support of debugging.
Generating the Soap Message
The service manager generates the soap message to send to the server based off of the params object you give it. In the above code example, it’s going to generate a soap message that looks like this:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <someMethod xmlns="http://webservicelocation/"> <someParameter>hello world</someParameter> </someMethod> </soap:Body> </soap:Envelope>
Handling the Result
The service manager requires you to give it a “result handler class.” This handler class gives you a hook to handle the soap result, before it’s handed off to your callbacks.
In the above example, there is this line (part of call props):
resultHandlerClass:MySoapResultHandler,
“MySoapResultHandler” needs to be a class that you create, to handle the result. Here’s that class:
package { import net.guttershark.support.servicemanager.soap.SoapFaultError; import net.guttershark.support.servicemanager.soap.SoapResultHandler; public class MySoapResultHandler extends SoapResultHandler { public var somePropertyOnTheResult; override protected function process():void { super.process(); super.setNamespace(); super.setBody(); super.setFault(); //if it get's passed this its an ok result. if(soapFault) throw new SoapFaultError(soapFault); somePropertyOnTheResult = soapBody.someResult.toString(); } } }
The reason we create this soap result handler class is to delegate processing of the result XML into this class, and abstract it out of your application and the service manager.
There’s one thing to note that’s special about the SoapResultHandler, if you throw a “SoapFaultErorr”, anywhere from the process method, it will be raised back up through the service manager, and trigger your “onFault” callback.
So, in the above handler class, if “soapFault” is set, it throws an error - as mentioned this will ultimately trigger your onFault callback.
Now let’s take a look at an updated “onResult” callback function from the Main class, to do something with our handler:
public function onSoapResult(sr:SoapResult):void { trace("result"); trace(sr.raw); //raw soap xml response. trace(sr.handler); //this is the instance of your result handler - an instance of MySoapResultHandler. trace(sr.handler.somePropertyOnTheResult); }
After the service manager processes your soap result handler, it calls your onResult handler. There’s a property on the SoapResult class called “handler”, which will be an instance of your handler class.
The service manager uses your handler class as a middle man so you can do what you want with the result - before it’s passed on. Hopefully you can see how nice this is. It keeps everything abstract from your application, and allows you to just focus on business logic. And also not clutter the methods that do all the work with a result.
Generally, for every soap method you’re going to call, you create a soap handler class. Each soap handler class can process the result specifically for that method call.
Other things to note
There’s one additional property you can set on a service call “showSoapRequest”:
sm.myWSDL.someMethod({ params:{someParameter:"hello world"}, resultHandlerClass:MySoapResultHandler, onResult:onSoapResult, onFault:onSoapFault, onFirstCall:onFirstCall, onMethodNotAvailable:onMethodNotAvailable, showSoapRequest:true, // });
This option will trace the generated soap request that’s being sent to the server.
Integrating Soap With The Model
Here’s a model file:
<xml version="1.0" encoding="utf-8"> <model> <services> <service id="mySoapService" wsdl="mySoapServiceWSDL" attempts="3" timeout="5000" /> <wsdl id="mySoapServiceWSDL" endpoint="http://www.example.com/wsdl" /> </services> </model>
And here’s the update class file:
package { import net.guttershark.control.DocumentController; public class Main extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"model.xml", initServices:true, } } override protected function setupComplete():void { callSoap(); } public function callSoap() { sm.myWSDL.someMethod({ params:{someParameter:"hello world"}, resultHandlerClass:MySoapResultHandler, onResult:onSoapResult, onFault:onSoapFault, onFirstCall:onFirstCall, onMethodNotAvailable:onMethodNotAvailable, showSoapRequest:true, }); } public function onFirstCall():void { trace("trying soap service"); showLoader(); } public function onSoapFault(sf:SoapFault):void { trace("fault"); } public function onSoapResult(sr:SoapResult):void { trace("result"); trace(sr.raw); trace(sr.handler); //instance of resultHandlerClass. trace(sr.handler.somePropertyOnTheResult); } public function onMethodNotAvailable():void { trace("method was not available on wsdl"); } } }
Crossdomain Issues
With SOAP, Flash has to send over a header called “SOAPAction”, so you’re crossdomains need to allow headers for this.
Conclusion
Soap services are greatly simplified with guttershark - which allows you to focus on business logic of you’re application. Additionally, I hate wasting time on code that’s already been written, don’t you?
Additionally, you should read through the source of the classes that support SOAP functionality. They are in “net.guttershark.support.servicemanager.soap.”
6 commentsGuttershark Service Manager: HTTP Calls
A key thing in flash that has to be done all too often is making some sort of service call. If you’re not using an AS3 api for a social service, like (cough twitter), it tends to be annoying to implement - because you always write the same code to accomplish some http call, but it’s just sending different parameters.
Guttershark has a service manager that handles doing all this for you, with built-in options for retries, timeouts, maximum attempts, and a bunch of other stuff. And it’s all integrated with the Model, or you can use it by itself. Let’s take a look.
Manually setting up the service manager:
package { import net.guttershark.managers.ServiceManager; import net.guttershark.support.servicemanager.shared.CallResult; public class Main extends Sprite { private var sm:ServiceManager; public function Main() { sm = ServiceManager.gi(); sm.createHTTPService("searchGoogle","http://www.google.com/",3,5000); var data:Object = {}; data.q = "Guttershark"; sm.searchGoogle({ method:"get", data:data, responseFormat:"text", onResult:onGoogleResult, onFault:onGoogleFault, onRetry:onGoogleRetry, onTimeout:onGoogleTimedOut, onFirstCall:onGoogleFirstCall, }); } public function onGoogleResult(cr:CallResult):void { trace(cr.result); hideStatusIndicator(); } public function onGoogleFault(cf:CallFault):void { trace("FAULT"); hideStatusIndicator(); } public function onGoogleFirstCall():void { trace("first call to google service."); statusIndicator.show(); } public function onGoogleRetry():void { trace("retrying google service"); } public function onGoogleTimedOut():void { trace("timedout"); hideStatusIndicator(); } private function hideStatusIndicator():void { statusIndicator.hide(); } } }
Quick side note, please bare with me - some of this is a mix of psuedo code just to illustrate how the callbacks can be used to control different elements.
The above code is somewhat obvious, except for one line:
sm.createHTTPService("searchGoogle","http://www.google.com/",3,5000);
This creates a service called “searchGoogle”, who’s endpoint is that URL.. 3 is the number of attempts to allow, and 5000 is the timeout for each attempt. So in this case, it will call the service once, if a result or fault doesn’t happen within 5000 milliseconds, another attempt will be fired, so on and so fourth.
The service manager, like a couple other classes in Guttershark is a dynamic classes. So, when you create the http service, you can access it like a property of the service manager (sm.searchGoogle).
Easy though right? Let’s look at how it’s integrated with the model:
Model XML:
<?xml version="1.0" encoding="utf-8"> <model> <services> <service id="searchGoogle" url="http://www.google.com/" attempts="3" timeout="5000" /> <services> </model>
Here’s an update Main class file that extends doc controller to integrate the model:
package { import net.guttershark.control.DocumentController; import net.guttershark.managers.ServiceManager; import net.guttershark.support.servicemanager.shared.CallResult; public class Main extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"model.xml", initServices:true } } override protected function setupComplete():void { var data:Object = {}; data.q = "Guttershark"; sm.searchGoogle({ method:"get", data:data, responseFormat:"text", onResult:onGoogleResult, onFault:onGoogleFault, onRetry:onGoogleRetry, onTimeout:onGoogleTimedOut, onFirstCall:onGoogleFirstCall, }); } public function onGoogleResult(cr:CallResult):void { trace(cr.result); hideStatusIndicator(); } public function onGoogleFault(cf:CallFault):void { trace("FAULT"); hideStatusIndicator(); } public function onGoogleFirstCall():void { trace("first call to google service."); statusIndicator.show(); } public function onGoogleRetry():void { trace("retrying google service"); } public function onGoogleTimedOut():void { trace("timedout"); hideStatusIndicator(); } private function hideStatusIndicator():void { statusIndicator.hide(); } } }
Response Formats
The service manager, handles a few different types of response formats: text, binary, xml, and variables. The service manager will intelligently handle results, based off of those types.
For example, let’s say you make a service call to get some XML data. Let’s assume the request completed with a 200 OK. But, there was an error on the server side that caused a result to be returned that isn’t the expected data. As long as structure the XML a certain way, the service manager will call you onFault callback. Here’s an example:
XML Response Format
The service call:
sm.createHTTPService("myXMLService","http://example.com/xmlService",3,5000); sm.myXMLService({method:"post",responseFormat:"xml",...});
Successful result (will call the onResult callback):
<?xml version="1.0" encoding="utf-8"> <response> <someData>Hello World</someData> </response>
Trigger a fault:
<?xml version="1.0" encoding="utf-8"> <response> <fault>There was an error</fault> </response>
Variables Response Format
The service call:
sm.createHTTPService("myXMLService","http://example.com/xmlService",3,5000); sm.myXMLService({method:"post",responseFormat:"variables",...});
Successful result (will call the onResult callback):
&name=Aaron&lname=Smith&site=codeendeavor.com
Trigger a fault:
&fault=There was an error
You can access the result variables as an object on the CallResult, for example:
function onResult(cr:CallResult):void { trace(cr.result.lname); }
The only response formats that are intelligently handled are variables and xml. The other formats are raw data so you’d have to handle it yourself in the result callback.
Routes with Web Frameworks
The service manager can also handle routes for http services. The general ideas is that the “service” is the base URL to the site that exposes routes, then you tell the service manager which routes to append. For example:
sm.createHTTPService("myRestfulSite","http://www.example.com/",3,5000); var username:String = "b00g3r"; sm.myRestfulService({method:"GET", routes:["user",username,"friends"], responseFormat:"xml"});
This example will end up calling “http://www.example.com/user/b00g3r/friends”.
Conclusion
That’s all there is for HTTP services with the service manager. The service manager also exposes remoting, and SOAP - in the same dynamic/simplistic manner.
I would recommend reading the source that supports HTTP services:
net.guttershark.managers.ServiceManager; net.guttershark.support.servicemanager.shared.BaseCall net.guttershark.support.servicemanager.shared.CallResult net.guttershark.support.servicemanager.shared.CallFault net.guttershark.support.servicemanager.http.Service net.guttershark.support.servicemanager.http.ServiceCall
I’ll show you the remoting, and SOAP support next.
No commentsBeing a Successful Development Team in Flash with Application Domains
A somewhat common problem I’ve run into a few times was how to “play nice” with other developers when working on projects that were quite big - and required a team effort. I’ve stumbled across a pretty cool way to build projects. Maybe others are aware of this, but I wasn’t, so maybe this can help you.
What I’m not talking about, is how to write better code so there aren’t any merge problems - or how to get along better. What I’ll explain is how you can alleviate pains with managing FLA files, and which developer can work on which FLA.
Here’s the situation: you have a shell file that loads some shared assets but more than one developer are trying to work in this assets FLA at the same time. You know version control presents a problem so you try to coordinate when and who is allowed to work on the FLA. Does that sound familiar? If not, maybe you’re already doing it right - maybe I can learn something from you.
Let’s think about this problem a little bit more, this is how I broke it down: we need a way to write code linked against some assets - which happen to live in a swf. The swfs will be used for part of a bigger application, but “the bigger application” doesn’t care which swf contains the assets or components.
The answer: write code that makes no assumptions about which swf the assets live in. How can you do that? Application domains. (I don’t really want to take the time to explain application domains - you can look through the livedocs for that.)
I’ll start to paint a very simple picture. We’ll have two final swfs:
main.swf (from main.fla) assets.swf (from assets.swf)
We have two developers on the team:
David, Sam
To start the project out, one developer will need to write some basic startup code, to kick everything off in the main swf. That startup code could do many things, but to keep this simple, let’s assume it just contains some assets for a preloader and the code to kick off the application when the required assets are loaded.
Once you get to that point, you can start to break things up. Here’s what you do: each developer creates his own FLA sandbox for developing components - which will be a work in progress FLA.
So now you have three FLA’s:
assets.fla assets_david.fla assets_sam.fla
This is where application domains, (with or without the help of guttershark) comes in. While your initially building the app, you load all three asset swfs. The model xml file would look like this:
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <asset libraryName="assets" src="assets.swf" preload="true" /> <asset libraryName="assetsDavid" src="assets_david.swf" preload="true" /> <asset libraryName="assetsSam" src="assets_sam.swf" preload="true" /> </assets> </model>
The PreloadController loads in all assets into the same application domain - which means your application code doesn’t need to do any extra work, or extra logic - just to get clips out of the libraries.
As developers are hitting milestones with building components, they can come back to the main application code and fit in their components. Naturally one developer will fall into being the maintainer of the overall application, or architect.
This is what you’ll start to see happen: the architect will be able to work on building the entire application, using the components from the other developers’ sandboxes, and whatever milestones are ready in the main assets swf - without disturbing the other developers, and the other developers won’t conflict with each other.
Can you see how that shapes up? All developers contribute to code, using version control systems to merge your application code together - which is standard. But, while each developer is doing his individual efforts, he builds out components and visual assets in his sandboxed FLA. The architect can use all those pieces, and nobody has to worry about conflicts. Yes!
Each developer will naturally hit milestones with the components he’s building. When this happens, he makes sure it’s clear to merge in his milestone into the final assets swf. Delete those clip assets from his sandbox, and continue.
The cycle is perfect. Now it’s all about individual milestone’s and quick merges vs. merge and conflict resolution at every commit or update because of crappy fla conflicts.
Obviously this is a very basic example, and I have by no means come up with some perfect team model. But I’ve successfully been doing this on one project I’m working on, and let me tell you - we haven’t had any major FLA conflicts, and we haven’t had to plan or even think about who, when, or where someone edits an FLA.
Do you have any experiences with team development? Do you have team techniques that eliminate conflicts with FLA’s? I’d definitely like to hear about them.
3 commentsThe Guttershark DocumentController
Continuing on from assets and the model, Let’s take a look at the DocumentController.
The DocumentController does quite a few things. I’ll show you three of them: loading model xml files, handling flashvars, and kicking off your app after everything is done.
The most important thing when writing flash websites, is that you pass in paths from flashvars. For example, passing in asset locations, xml locations, and of course, what the model xml file is.
Flashvars are great - they allow you to put path configuration in javascript, which gives you a hook right before the swf is embedded. That can be used for making decisions based off of the url it’s being embedded at. That can also lead to a problem - how do I handle flashvars when I’m publishing the FLA in the Flash IDE? Should I just publish, but then close the window and open the browser? Or should I put some hard coded information in the main class, for that situation?
This problem is fixed with the guttershark DocumentController. The method DocumentController.flashvarsForStandalone is called when you run the swf in the Flash IDE, or as a standalone swf.
For the first example, let’s assume you are setting it up to only run from HTML.
Here’s the HTML snippet:
<!-- ... -->
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml"
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script>
<!-- ... --> Simple right? The guttershark document controller looks for a flashvar called “model”, if found, the doc controller will load that xml, and give it to the Model.
Here’s the document controller:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function setupComplete():void { trace( ml.xml ); } } }
The setupComplete method is a hook for you. It’s essentially your “constructor” for the application, it runs after all startup logic is done. And in our case, it’s only loading the model xml.
If you try this out, you’ll notice that when you’re in the Flash IDE, the trace will give you “null” - there’s that problem I mentioned. We need some way of giving the document controller “alternative” flashvars when running in standalone mode. Here’s how to integrate that:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml" } } override protected function setupComplete():void { trace( ml.xml ); } } }
The flashvarsForStandalone method is called as one of the first method calls in the doc controller startup process - and it only get’s called if the swf is indeed running as the standalone player. If the swf is embedded on an HTML page, this method is ignored.
So, with this in mind, let’s integrate our “path” logic from the previous post.
Here’s the updated HTML snippet:
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml",
assets:"assets/",
bitmaps:"assets/bitmaps/",
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script> Here’s the updated code:
package { import net.guttershark.control.DocumentController; public class TestPreloader extends DocumentController { override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml", assets:"assets/", bitmaps:"assets/bitmaps/" } } override protected function initPaths():void { //"ml" is already a protected property on doc controller. ml.addPath("bitmaps",flashvars.bitmaps); } override protected function setupComplete():void { trace( ml.xml ); } } }
The document controller has a property called “flashvars”. If the swf is in standalone, it will be set to the object you return from flashvarsForStandalone, or if the swf is embedded in HTML, it will be set to the flashvars that are embedded on the swf.
There’s one new method to point out - initPaths. This method is a hook that the doc controller gives you. It’s called early in the setup process (but after flashvars are ready). Because the initPaths is called after the flashvar logic, all you need to do is add paths to the Model from flashvars. So, in our case, the only path we need is “bitmaps”.
One other key piece of information - the flashvars from HTML and flashvarsForStandalone don’t need to define the exact same values. This is very important. What if when you’re developing in the FlashIDE, you need to change the paths to be relative, but when you deploy the site, the paths need to be absolute? This is exactly why you have an “alternative” way to define flashvars for standalone.
Now, let’s go ahead and integrate everything from the last two posts.
Here’s the model xml:
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <group id="pictures"> <asset libraryName="myPicture" src="myPicture.jpg" path="bitmaps" preload="true" /> <asset libraryName="myOtherPicture" src="myOtherPicture.jpg" path="bitmaps" preload="true" /> </group> </assets> </model>
Make note of the new “preload” attribute above.
Here’s the HTML again:
<!-- ... -->
<div id="flash"></div>
<script>
var vars={
model:"assets/xml/model.xml",
assets:"assets/",
bitmaps:"assets/bitmaps/"
}
var params = {scale:'noScale'}
var attributes = {id:'flaswf',name:'flaswf'}
swfobject.embedSWF("main.swf","flash","600","400","9.0.0",null,vars,params,attributes)
</script>
<!-- ... -->And finally, here’s the entire document controller code:
package { /* .. */ public class TestPreloader extends DocumentController { public var progress:MovieClip; override protected function flashvarsForStandalone():Object { return { model:"assets/xml/model.xml", assets:"assets/" bitmaps:"assets/bitmaps/" } } override protected function initPaths():void { ml.addPath("bitmaps",flashvars.bitmaps); } override protected function setupComplete():void { trace( ml.xml ); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); pc.addEventListener(Event.COMPLETE,onPreloadComplete); pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); /* new method - getAssetsForPreload() */ pc.addItems(ml.getAssetsForPreload()); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
This is starting to shape up nicely.
There was one method added here, getAssetsForPreload(). This method returns an array of all Asset definitions from the model that contain the “preload=true” attribute.
Hopefully you can see how helpful this all is, and how guttershark makes it simple!
It’s also very important to note that there isn’t anything “hard-coded” anymore, with the exception of the initFlashvarsForStandalone - which is only for flash IDE development anyway.
And, sure, we still have the string “myPicture” hard coded. But, what you start to see is that you change everything around the asset “myPicture”. myPicture is now just an ID to some asset, and we can easily change everything else about the application, and as long as the paths from flashvars are pointing to the right location - it will all work.
Conclusion
It’s great! We went from having quite a bit of code with hard coded assets in it. To less code, that does everything for me.
You can also start to see how nothing in Guttershark is really bound to anything else. If you wanted to write a new document controller to replace guttersharks built in doc controller - you certainly could do that. All while being able to use the rest of guttershark - like the model, asset manager, preloaders, etc.
Where do you go from here? I would strongly suggest reading the source code for the DocumentController, Model, PreloadController, and AssetManager, CoreClip, and CoreSprite. Those are the most important pieces of Guttershark. Everything else is gravy on top.
11 commentsAssets and the Model Class
Continuing on from Preloading and Asset Management. Let’s look at how the model can help with asset definitions, and removing hard coded assets.
The Model class in Guttershark is one of a couple “brains” in the library. It can do lots of stuff for you, but today, we’ll just focus on assets and paths. I’m going to continue using the example from Preloading and Asset Management - just adding and removing some code.
Before we get too far into this, make note of the PreloadController.addItems method. Notice that it takes an array of items (Asset instances).
Now take a look at these methods on the Model class. (getAssetByLibraryName, getAssetsByLibraryNames, getAssetGroup).
The Model class works with an XML object - which must be structured a certain way, then the model can do all this work for you.
To start getting this all setup, let’s first look at the most basic XML file, which defines some assets. This xml will show you how to use two of the model methods.
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <!-- for the model method, getAssetsByLibraryNames --> <asset libraryName="myPicture" src="assets/myPicture.jpg" /> <asset libraryName="myOtherPicture" src="assets/myOtherPicture.jpg" /> <!-- for the model method, getAssetGroup --> <group id="pictures"> <asset libraryName="myPicture" src="assets/myPicture.jpg" /> <asset libraryName="myOtherPicture" src="assets/myOtherPicture.jpg" /> </group> </assets> </model>
Ok, so with that in mind - here’s our first example of using this:
package { import flash.events.Event; import flash.net.URLRequest; import flash.display.MovieClip; import flash.display.Sprite; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; import net.guttershark.util.XMLLoader; import net.guttershark.model.Model; public class TestPreloader extends Sprite { private var ml:Model; private var am:AssetManager; private var pc:PreloadController; private var assets:Array; private var xmlLoader:XMLLoader; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); loadModelXML(); } private function loadModelXML():void { xmlLoader=new XMLLoader(); xmlLoader.contentLoader.addEventListener(Event.COMPLETE,onModelLoaded); xmlLoader.load(new URLRequest("model.xml")); } private function onModelLoaded(e:*):void { ml=Model.gi(); ml.xml=new XML(xmlLoader.data); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); //remember.. 300 = the pixelsToFill. pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. /* first way to get assets, using getAssetsByLibraryNames */ assets = ml.getAssetsByLibraryNames("myPicture","myOtherPicture"); /* second way, using groups */ assets = ml.getAssetGroup("pictures"); pc.addItems(assets); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
It’s simple right? But wait, there’s more!
The model xml has some room for improvement - getting rid of the asset paths. For example, in the model xml, each asset defines the “src” attribute to equal something like: “assets/XXX”. It would be nice to not have to define that all the time. So how can we change it?
The Model and Paths
The Model class has two methods for working with paths. addPath and getPath.
Before we integrate these methods into the code, here’s a sand-boxed code example of how the path methods work:
var ml=Model.gi(); //the preferred way: ml.addPath("root","http://example.com/"); ml.addPath("assets", ml.getPath("root")+"assets/") ); ml.adddPath("xml", ml.getPath("assets")+"xml/") ); trace( ml.getPath("xml") ); // http://example.com/assets/xml/ //but, you can also define just the pieces of a path, and use ...rest parameters with the getPath method. ml.addPath("root","http://example.com/"); ml.addPath("assets","assets/"); ml.addPath("xml","xml/"); trace( ml.getPath("root","assets","xml") ); //http://example.com/asset/xml/
By convention, you should always add trailing slashes at the end of a path. This makes sure you never have to write parse code to remove double slashes (//), or testing to see if the index of a slash is the last character, etc.
That’s pretty easy right? We can integrate that into the model xml, and code.
Here’s the new model xml, note that I’m only going to use one style for grabbing assets - the group ids.
<?xml version="1.0" encoding="utf-8"?> <model> <assets> <group id="pictures"> <asset libraryName="myPicture" src="myPicture.jpg" path="bitmaps" /> <asset libraryName="myOtherPicture" src="myOtherPicture.jpg" path="bitmaps" /> </group> </assets> </model>
Notice the new attribute, “path”. All of the methods on the Model class that help with getting you assets, will look for this path attribute, and then look in the model for path definitions that match that, and prepend it onto the src attribute.
Here’s our updated example:
package { import flash.events.Event; import flash.net.URLRequest; import flash.display.MovieClip; import flash.display.Sprite; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; import net.guttershark.util.XMLLoader; import net.guttershark.model.Model; public class TestPreloader extends Sprite { private var ml:Model; private var am:AssetManager; private var pc:PreloadController; private var assets:Array; private var xmlLoader:XMLLoader; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); loadModelXML(); } private function loadModelXML():void { xmlLoader=new XMLLoader(); xmlLoader.contentLoader.addEventListener(Event.COMPLETE,onModelLoaded); xmlLoader.load(new URLRequest("model.xml")); } private function onModelLoaded(e:*):void { ml=Model.gi(); ml.xml=new XML(xmlLoader.data); /** define paths. **/ ml.addPath("bitmaps","assets/bitmaps/"); preloadAssets(); } private function preloadAssets():void { pc = new PreloadController(300); //remember.. 300 = the pixelsToFill. pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. assets = ml.getAssetGroup("pictures"); pc.addItems(assets); pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.pixels; } } }
There’s only one minor change to the code, I’ve added one line that defines the “bitmaps” path with the model.
Conclusion
That’s all there is to assets and paths with the model class. Hopefully you begin to see how much work Guttershark can really do for you. Guttershark is all about eliminating code. I hate writing code, don’t you? I actually love writing code, but the code that I hate writing is the same code i’ve already written on another project.
You may have noticed, there’s even more room for improvement. There are a few things here that should definitely not have to be written again. And there’s still a few things that are “hard coded”.
What I’ll show you next is the DocumentController. It takes care of loading your model xml, and gives you hooks to initialize paths with the model.
With Guttershark and the DocumentController, you’ll see that this example we’ve been working on will be significantly reduced - imagine that, event less code?
9 commentsPreloading and Asset Management with Guttershark
I thought I’d share a quick article on how and why preloading, and asset management is so simple with guttershark.
The preload controller is designed to be the one access point for loading any type of files. As some of you might be snooping through the guttershark library - you’ll see that there are multiple classes that help do the work for the PreloadController, which are called “Workers”. Don’t be fooled by all the support classes - you don’t ever need to use them.
Basic example
Let’s look at an example, this only sets up the preloader to load in some assets, we’ll look at getting them out in a bit.:
package { import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; public class TestPreloader extends Sprite { private var pc:PreloadController; private var assets:Array; public function TestPreloader() { pc = new PreloadController(); pc.addEventListener(Event.COMPLETE,onPreloadComplete); //when preloading all items complete. pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //progress for all items as a whole. //pc.addEventListener(AssetProgressEvent,onAssetProgress); //fired for every asset's individual progress. //**there are move events available. see docs for those.** //now let's add some assets assets=[]; assets.push( new Asset("assets/myPicture.jpg") ); assets.push( new Asset("assets/myOtherPicture.jpg") ); //add the assets pc.addItems(assets); //now let's kick off the preloader pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); } private function onProgress(ppe:PreloadProgressEvent):void { trace(ppe); } } }
What you’re looking at is the most basic form of preloading with guttershark. In the above example, we’re loading jpg’s. Internally to the Asset class, it uses a “BitmapWorker” to load in the asset. This is something you don’t have to specify, it will figure out what type of content your asset is based off of the file extension.
That raises a question - what about loading files that don’t have an extension? With asset instances, you can specify a “forced file type”, which informs the Asset of which worker to use. For example:
var rssAsset:Asset = new Asset("http://example.com/rss?feed=atom",null,"xml");
Ignore the second “null” parameter for now, I’ll get to that. The third parameter, is how you set a “forcedFileType”. So this asset will be loaded with the XMLWorker.
Showing progress
Guttershark has some cool conventions on multiple classes, which will automatically give you a pixel value based on some other type of value, such as progress, time, etc.
The PreloadProgressEvent has a property called “pixels”. This value is based off of what you tell the preloader to “fill”. For example, let’s say we wanted our preloader to fill 300 pixels. Keep in mind, what you do with this “pixel” value is up to you. It could be mapped to height, width, etc. Here’s the example (this is in addition to the above example):
package { /*..*/ public class TestPreloader extends Sprite { /*..*/ public var progress:MovieClip; public function TestPreloader() { pc = new PreloadController(300); //(this is key, it specifies the "pixelsToFill") /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); } private function onProgress(ppe:PreloadProgessEvent):void { /*..*/ progress.width=ppe.pixels; } } }
In the above example, the first argument to the preload controller constructor is the “pixelsToFill” property. Which is how the pixel value is delivered to you in the event handler.
There are a few other classes that offer this same type of behavior. The FLV class is another example - it will give you a “pixelsPlayed” property. Which you map to a progress bar. Read more of the guttershark docs for that.
Ok, that covers the majority of working with the preload controller. There are a few other methods, and properties that may be of use. You can check those out in the docs.
On to using the assets.
Accessing Assets with the AssetManager
Every preload controller you use stores all of the assets loaded in the AssetManager. The asset manager is a singleton - this creates a globally accessible asset library, so it doesn’t matter in which class, or in what scope assets are loaded - they all get into the asset manager.
The preload controller also (by default), loads all assets into the same application domain. You’ll see what that does in a bit.
Remember that “null” parameter for the Asset class from a few paragraphs ago? That parameter is called the “libraryName”. When you specify the library name for an asset, it registers that asset in the asset manager by that id.
So, to slightly alter the example so far, to attach a bitmap onto the stage. It would look like this (here’s the complete example):
package { import flash.events.Event; import net.guttershark.control.PreloadController; import net.guttershark.support.preloading.Asset; import net.guttershark.support.preloading.events.PreloadProgressEvent; public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(300); pc.addEventListener(Event.COMPLETE,onPreloadComplete); pc.addEventListener(PreloadProgessEvent.PROGRESS,onProgress); //now let's add some assets assets=[]; assets.push( new Asset("assets/myPicture.jpg", "myPicture" ) ); assets.push( new Asset("assets/myOtherPicture.jpg", "myOtherPicture") ); //add the assets pc.addItems(assets); //now let's kick off the preloader pc.start(); } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getBitmap("myPicture") ); addChild( am.getBitmap("myOtherPicture") ); } private function onProgress(ppe:PreloadProgessEvent):void { trace(ppe); progress.width=ppe.width; } } }
Again, the important piece of code in this example, is the addition of the second parameter to the Asset instance. Each one has a library name, which will make that asset available in the asset manager.
For another example, let’s look at how you can load swfs, and grab assets out of the swf’s library, I’ve altered the asset that is loaded, and what happens when the preload is complete.:
package { /**/ public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(); /*..*/ //now let's add some assets assets=[]; assets.push( new Asset("assets/myAssetsSWF.swf", "assets" ) ); /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getMovieClipFromSWFLibrary("assets", "myMovieClip") ); } /* .. */ } }
You should hopefully start to see some of the elegance with the preload controller, and asset manager.
How do application domains play into this picture? In the above example, I’m calling the method “getMovieClipFromSWFLibrary”. This method does like it sounds, it grabs an asset out of that swf’s application domain. There are other methods to get other assets as well, like getBitmapFromSWFLibrary, getSpriteFromSWFLibrary, etc.
That’s well and good, but we can take this further - because the preload controller loads everything into one application domain, technically you don’t have to tell the asset manager which swf that asset is in. So, we could alter this example like this:
package { /**/ public class TestPreloader extends Sprite { private var am:AssetManager; private var pc:PreloadController; private var assets:Array; public var progress:MovieClip; public function TestPreloader() { am = AssetManager.gi(); pc = new PreloadController(); /*..*/ //now let's add some assets assets=[]; assets.push( new Asset("assets/myAssetsSWF.swf", "assets" ) ); /*..*/ } private function onPreloadComplete(e:Event):void { trace("preloading complete"); addChild( am.getMovieClip("myMovieClip") ); } /* .. */ } }
Now you can see, that it doesn’t matter which swf the clip “myMovieClip” is defined in, because the swf is merged into one application domain.
This does raise one problem. Multiple library items, with the same export name, in different swfs. For example, if I have two swfs (swf1, swf2), and they both have the clip “GlossButton”. The last one to be loaded in will be discarded. If swf2 is loaded after swf1, the GlossButton in swf2 will be discarded. That’s the only pitfall to watch out for when using one application domain.
Conclusion
The PreloadController can load anything for you. It can load bitmap, swf, flv, xml, audio, and stylesheet (css) files.
Let me quickly note - manually creating Asset instances is a no-no! I never actually create Asset instances in code, the examples were to illustrate how the preload controller works.
So, in the next post, I’ll show you how to get rid of that annoying code that requires you to manually create Asset instances - we’ll do that with XML and the Model class.
And as always, read the docs.
2 commentsXML Namespaces in AS3: XMLNamespaceProxy
Quick note about a neato class I just added to guttershark. I’ve been working with SOAP in AS3 lately. Man is it a pain in the butt. So I’m doing my best to make sure I don’t have to write this crap again.
One other quick note before I show you the namespace proxy. I’ve integrated a SoapService in with the ServiceManager. It’s fairly easy to setup now. You can read all about that in the docs.
On to the XMLNamespaceProxy. With soap, when you get a result back - there are a couple steps you need to go through. Usually looks something like this:
var result:XML=new XML(mySoapResultXML); var ns:Namespace=result.namespace(); var body:XML=result.ns::Body;
This sets you up with whatever is in the body of the soap result. Which is pretty simple. But now I have to snoop through the content of the body - which in my case (the service I’m using) has custom namespaces in the result. Here’s my initial code looked like:
var targetNodes:XML=body.ns::rawValues.ns::bud09Out; trace(targetNodes.ns::totalIncome); //give me a value im looking for
The service I’m hitting has namespaces in the responses, so in order to find the actual values needed I have to prefix everything with the qualifying namespace. Arg!
Here’s entire code snippet with xml namespace proxy integrated:
var result:XML=new XML(mySoapResultXML); var ns:Namespace=result.namespace(); var body:XML=result.ns::Body; var targetNodes:XML=body.ns::rawValues.ns::bud09Out; //the good stuff: var xnp:XMLNamespaceProxy=new XMLNamespaceProxy(targetNodes,ns); //now I can use the xml namespace proxy just like normal: trace(xnp.totalIncome); //or... trace(xnp.years.year[0].@category);
After you create an XMLNamespaceProxy object, every call get’s prefixed with the qualifying namespace and forwarded to the actual XML object. No more namespace prefix’s. Yay!
As you’re reading through the XML. The proxy will return instances of XMLNamespaceProxy’s (if the target is XML), or it will return a normal XMLList which you can iterate over.
And as usual, check the docs.
Enjoy!
4 commentsChain-able Decorators in AS3 With Proxy
[UPDATE: Some of the source files in this example won't be available. Please see the latest files in the guttershark repo, in gs.display.decorators]
I was briefly examining the decorator pattern in the “Advanced ActionScript 3 with Design Patterns,” which is great. But the mention of “chaining decorators” wasn’t fully brought to fruition.
I spent some time thinking about how exactly one would implement them with display objects. At first examination, the problem is that with a decorator, it needs to implement the same interface as the decorated object - which presents a problem. I don’t want to write the entire movie clip, or sprite methods. Bleh.
The solution is to create a decorator that extends proxy. If you call a method, or read a property on the decorator that’s unknown, forward the call to decorated object. Another key, is to make sure the decorator requires a dynamic object as the object to decorate.
Maybe that description was a little bit hard to understand. These examples should help explain:
First, make note of these two includes:
Then take a gander through these three decorators:
Optionally, with the “highlightable” sprite. It supports something called proxy through. Which is just a creative way for me to specify a different object to send unknown method calls or properties to. That’s what the second include is for.
The only awkward thing is that you can’t use these decorators with the display list. So you’d still have to add the base object to the display list, but then you can chain the decorators and use the outer-most decorator for methods/properties.
The examples zip file has updates for this new functionality.
Make note, that because the decorators use includes, you’ll need to make sure you’ve got the as and as_includes folder from the guttershark source. The reason the includes are outside of the classpath is because asdoc interprets the .as files as a class, which causes errors. And the reason for the includes in general is so I don’t have to repeat myself.
Enjoy.
4 commentsJSFL Proxy Class - Alleviate MMExecute Pain
Those of you who have written JSFL know the pain of MMExecute - with having to wrap quotes, inside of double quotes, which could have more quotes or slashes. I’ve been writing a number of WindowSWF tools - which use a lot of JSFL. This JSFL Proxy class will help alleviate all the work.
The JSFLProxy class alleviates calling JSFL scripts, or methods inside of a JSFL script. It also adds support for escaping parameters, unescaping the responses, and allowing you to specify a return type from JSFL - which, amazingly (or not so) enough, allows me to cast the JSFL response before it’s returned to you.
Here’s a demonstration:
var jp:JSFLProxy=JSFLProxy.gi(); var functionLib:String="file://XXX/myFunctionLibrary.jsfl"; var dir:String = jp.runScript(functionLib,{method:"getSomeDirectory",params:[theDirectory],responseWasEscaped:true}); var dirContents:Array = jp.runScript(functionLib,{method:"getDirContents",escapeParams:true,responseFormat:"array",parameters:[dir],responseWasEscaped:true});
There are a few other methods available, and support for a few other response formats. JSFLProxy is available in Guttershark. Here’s a direct link to the JSFLProxy documentation.
No comments