Update — August 6, 2010: I found a bug in my original demo which prevented the deep linking feature from working at startup. See the full description at the end of this post.
I started using Robotlegs on a project recently and last week I needed to add SWFAddress into my project for its deep linking features. So, I thought that I’d search the net for examples or tutorials on combining the two. The only one I could find was the one by Alexandru Petrescu which combines Robotlegs, SWFAddress and AS3Signals. I’ve been curious about AS3Signals but haven’t gotten around to trying it out yet so I was reluctant to add a third new (to me) technology into the mix. Also, Alexandru’s project uses a modified version of SWFAddress and I didn’t want to jump off the main SWFAddress trunk. So, I decided to create this example for my own edification and, hopefully, for others. At the end of this post is a set of instructions for integrating the SWFAddress proxy class into your Robotlegs application.
Multiple Approaches Possible
Since starting this blog I’ve found that writing about my Flex learning adventures has helped to clarify my thinking and improve my development process. While writing blog posts I’ve frequently found that I didn’t fully understand some aspect of the technology I was writing about. Having to explain it forced me to go back and dig deeper so that I acquired a better understanding. I’ve also found that I’ve made errors and, again, discussing the code has exposed them.
This particular post is a case in point. After initially completing this write-up I discovered that I hadn’t actually accomplished (in the code) what I had set out to do so I went back and reexamined my options. In the end, I found three different approaches to combining the use of Robotlegs and SWFAddress. As a result, I ended up rewriting the code and this post. In this post I’m showing examples of two of the three approaches I considered.
Here is the Robotlegs SWFAddress demo application.
Using a Proxy for All Interaction with SWFAddress
While learning about SWFAddress I had been reading some best practices and bad practices articles about using SWFAddress and had come across several warnings to keep usage of it centralized into a single proxy class. That’s what the Flex example supplied with SWFAddress does. Its proxy class is called Helper. Within Helper.as are an event handler which receives SWFAddressEvents and a function that updates SWFAddress whenever the app state changes.
So, I started my example by creating a SWFAddressProxy class with the intention of routing all access to SWFAddress through that class in the same way that the Helper class does. This is designed to eliminate conflicting requests for state changes and to simplify debugging. Similarly, all state change requests are routed through the PageChangeCommand class. (Although there aren’t actually any logical pages—only states—in this example, that’s the model that this prototype is intended to support.)
What I found was that the most natural approach with Robotlegs would not be to use the proxy class. In Robotlegs events dispatched by a view are routed to its mediator and that’s what I originally did with the SWFAddressEvent. I set up a button click listener in the view, as is done in the SWFAddress Flex example, and then forwarded the events to the view’s mediator. That worked but that meant the proxy class was only doing half the job (essentially, the part done by the setSWFAddress function in the SWFAddress Helper proxy class).
When I realized that I wasn’t really using the proxy correctly I considered two alternatives:
- moving the handling of SWFAddressEvents to the proxy and
- simply dispensing with the proxy.
I ended up implementing both and offer them here (Flex 4):
Robotlegs Initialization and Event Routing
The sample without the proxy is essentially the same as the one with the proxy except:
- handling SWFAddressEvents takes place in SWFAddressMediator and
- the PageChangeCommand class communicates directly with SWFAddress instead of communicating through the proxy.
This approach actually feels a little cleaner to me as the handling of the PageChangeEvent code occurs in only a single class instead of two and it also results in clearer, leaner code. Of course, this sacrifices the benefit of having all the SWFAddress code isolated in the proxy class, so on with the proxy approach.
Startup in the SWFAddress Proxy Version
In the rest of this post I’ll discuss only the sample with the proxy. At app startup an instance of a Robotlegs context (SWFAddrContext) is created. This is the standard way of starting a Robotlegs application. In the SWFAddrContext startup method the various views, mediators and commands are wired up. First, the view is mapped to its mediator:
mediatorMap.mapView(SWFAddressView, SWFAddressMediator);
Any events that are dispatched from the view can now be handled in the mediator.
Next, the view itself is mapped (contextView is a property of the Robotlegs Context class):
injector.mapValue(SWFAddressView, contextView);
This allows the view to be injected wherever needed (in this example, into SWFAddressMediator and PageChangeCommand). By using mapValue instead of one of the other map functions a specific instance of the view is the one that is injected whenever requested. Since SWFAddressView.mxml is the default (and only) application in this project an instance of it is created automatically at startup. So, we tell Robotlegs to use this instance rather than using one of the other Robotlegs map functions which can automatically create an instance of the specified class.
Similarly, we map the SWFAddressProxy class so that it can be injected where it is needed (PageChangeCommand). In this case, though, we let Robotlegs handle creation of the instance, upon demand, through use of the mapSingleton method:
injector.mapSingleton(SWFAddressProxy);
This tells Robotlegs to create a single instance of SWFAddressProxy and to provide a reference to it whenever it is requested (injected).
Our last task in the context startup function is to map the PageChangeEvent to the PageChangeCommand:
commandMap.mapEvent(PageChangeEvent.PAGE_CHANGE_EVENT, PageChangeCommand, PageChangeEvent);
This way, whenever a PageChangeEvent is dispatched it will automatically be routed to PageChangeCommand. PageChangeCommand’s execute method tells the view to change to the requested state and then it updates SWFAddress, through the proxy class, with the current state.
There is one additional step that is part of the Robotlegs startup process. This is handled in SWFAddressMediator.onRegister which is called automatically by Robotlegs:
eventMap.mapListener(_swfAddressView, MenuEvent.MENU_EVENT, onPageChangeRequest, MenuEvent);
This line of code maps MenuEvent, which is dispatched by the view, to the onPageChangeRequest listener in the mediator. A MenuEvent is dispatched by the button listeners when the user clicks one of the buttons. Here is onPageChangeRequest:
private function onPageChangeRequest(event:MenuEvent):void { var pageChangeEvent:PageChangeEvent = new PageChangeEvent(PageChangeEvent.PAGE_CHANGE_EVENT); pageChangeEvent.pageToChangeTo = event.pageToChangeTo; dispatch(pageChangeEvent); // Have to use Robotlegs dispatch method since dispatchEvent is not a member of this class. }
SWFAddress Proxy
The other key thing that happens at startup is that the SWFAddressProxy registers itself in its constructor as the listener for SWFAddressEvents:
SWFAddress.addEventListener(SWFAddressEvent.CHANGE, onSWFAddressChange);
Handling State/Page Change Requests
State/page change requests can now originate in two ways:
- The user clicks one of the three buttons.
- The user uses the Back/Forward browser buttons to request a return to a previously viewed state/page.
In the first case a MenuEvent is dispatched by the view and is handled in SWFAddressMediator which then dispatches a PageChangeEvent to be handled by the PageChangeCommand class. In the second case, a SWFAddressEvent is received by SWFAddressProxy.onSWFAddressChange. Inside this listener a PageChangeEvent is dispatched which PageChangeCommand also receives. PageChangeCommand receives the same events regardless of where they originate.
PageChangeCommand.execute checks to see if the request is for the same state as the view is currently in and, if so, ignores the request. Otherwise, it tells the view to change its state and notifies SWFAddress, through SWFAddressProxy, of the change:
override public function execute():void { // Check to see if the current state is the same as the requested state and, if so, don't do anything. // Normally, the current page would probably be stored in the model but for this simple example we don't have a model. if(_event.pageToChangeTo == _swfAddressView.getCurrentPage()) return; <br /> _swfAddressView.changeState(_event.pageToChangeTo); _swfAddressProxy.setValue(_event.pageToChangeTo); _swfAddressProxy.setTitle(_event.pageToChangeTo); }
Adding this SWFAddress Proxy to an Existing Robotlegs Project
To integrate this proxy with an existing Robotlegs project follow these steps:
- Add the SWFAddress.swc to your project.
- Copy swfaddress.js to your project.
- Include the JavaScript file in your HTML page:
<script src="swfaddress.js" type="text/javascript" charset="utf-8"></script>
Note that the SWFAddress docs say this must be included after the <script> tag for swfobject.js. - Add SWFAddressProxy.as to your project and edit the onSWFAddressChange function as needed.
- Inject the proxy into your controller:
[Inject]
public var _swfAddressProxy:SWFAddressProxy; - In your controller, add the following lines, replacing the function parameter as appropriate for your project:
_swfAddressProxy.setValue(_event.pageToChangeTo);
_swfAddressProxy.setTitle(_event.pageToChangeTo); - Map SWFAddressProxy in your Robotlegs context; e.g.,:
injector.mapSingleton(SWFAddressProxy); - In your controller, you will want to make sure you check to see if the new page request is the same as the current page and, if so, do nothing. This is because SWFAddress always dispatches a change after you call setValue. This means that your controller will be invoked a second time for each page change.
- (You probably also want to turn off the "Enable integration with browser navigation" option in the Flex Compiler settings dialog. Note that if you do this after step 3 above Flash Builder will rewrite your index.template.html so you’ll need to make that change again.)
Conclusion
With this setup, all interaction with SWFAddress is handled through SWFAddressProxy and all page/state change requests are handled through PageChangeCommand. This centralization of handling should simplify development and debugging. I’ve just implemented this model in my project and initial indications are that it will work fine. If I later discover otherwise, I’ll post an update. I’d also be interested in your thoughts on the two approaches.
Update — August 6, 2010: Fix Failure to Go to Requested State at Startup
My initial implementation worked fine when clicking buttons to change states. It also worked fine if you subsequently entered a URL with a specific state into the browser.
But what it didn’t do was to enter the desired state upon initial page load. The reason is because, by default, Robotlegs defers instantiation of objects to their first request. This meant the proxy was unavailable to respond to the URL state fragment upon initial load.
The fix is simple—instruct Robotlegs to instantiate the object immediately. This is done with a single line of code:
injector.instantiate(SWFAddressProxy);
Adding this line of code immediately after the line where the object is mapped:
injector.mapSingleton(SWFAddressProxy);
causes the object to be created at startup so that it can respond immediately. This ensures that internal "pages" which are bookmarked will be displayed at initial page load.
The demo and the download, linked above, have been updated with this additional line of code
{ 4 comments… read them below or add one }
Regarding your Update at the end of the post: you should use getInstance() rather than instantiate() – see the asdocs for the difference.
Shaun,
Thanks for the feedback. I just read the docs for getInstance() and instantiate() and it sounds like the main difference is that instantiate will always create a new instance while getInstance will create a new instance only if one does not yet exist. I could see where calling instantiate could lead to the creation of unnecessary objects in some scenarios. But I don’t see that as a problem here. It’s my understanding that Context.startup is called only once for a Context instance. So, I would expect that only a single instance of SWFAddressProxy would ever be created.
The instantiate() docs do discuss other aspects related to using dependency injectors other than the default SwiftSuspenders. I’m a bit vague on those issues but, here too, that doesn’t seem to be a concern for this example since I’m using the default injector.
Finally, getInstance takes as a parameter a String name variable, presumably so that the caller can request a specific instance for the use case where multiple instances of a given class are being used. That doesn’t seem to be an issue here either.
Maybe I’m missing something, though. If that’s the case, I’d appreciate knowing what.
Regards,
Dave
Hi Dave.
Nice post – thanks for the provided code and walk through. Btw. shaun wrote RobotLegs so you better do what he says
Keep up the good work!
Hi Thomas,
Thanks for the feedback. And, yes I know about shaun & RobotLegs. I just wish I had a better understanding of why he made that suggestion.
Cheers,
Dave
{ 1 trackback }