Flex 3:Feature Introductions: Deep Linking
From Adobe Labs
Back Button and other URL Support
Introduction
"Deep Linking" is a term used by web developers to describe support for URL-based navigation in applications that are not a traditional hierarchy of HTML pages.
Traditional web applications have multiple valid URLs. For example, if you go to http://www.adobe.com, you'll end up at our home page. If you press the "Support" button, you'll end up at the Adobe support page and your browser will show the URL http://www.adobe.com/support/. Choose "Contact Support" and you'll end up at http://www.adobe.com/support/contact/. You can hit the back button to go back to the previous page, bookmark the support page so you can go there directly, go back to the support page much later since it's added to the browser history, and someone else can type in http://www.adobe.com/support/ and go to the support page without seeing the home page.
By default, Flex (and AJAX) applications don't work this way. Your Flex application is accessible via one URL and you change what the user sees within the Flex application instead of switching to a new URL. In fact, switching to a new URL closes your application and, while it could open another Flex application at the new URL, you would lose the ability to make nice transitions between the visual states of your application and make it harder to share data between the application states, so typically you want to work within one URL, but then you don't have the navigation features users expect such as back button, bookmarking, etc.
Deep Linking allows the URL to represent different "places" in a Flex application without closing the application as you change the URL. This is accomplished by using real or faked named anchors (depending on which browser you are using). Named anchors are parameters that follow the "#" in a URL that are normally used to navigate to different places within an HTML page.
For example, if you click on this link Example, you will go the the example portion of this document and the URL will show an additional #Example. You can put just about anything after the "#" and changing what is there doesn't cause the page to re-load; therefore, the Flex application can remain running. The portion of the URL after the "#" is known as the fragment. If you change the fragment, the previous fragment gets stored in the browser history.
The Flex 3.0 Deep Linking Implementation
In Flex 3.0, Deep Linking is implemented via the BrowserManager. The BrowserManager allows your application to set the fragment and receive notification when the fragment changes. There are new methods in URLUtils that help you convert an object full of property and value pairs to a fragment and vice-versa, but there is no support for creating or interpreting fragments directly from the components that make up the application. We've left this out for two reasons: One is that we want to eventually build a more automated way of providing deep linking in an application. However, that's a big chunk of work and we don't want to start down that path right now and have backward compatibility issues later. The second reason is that there is a pretty good third party implementation called UrlKit. However, it is unlikely that anything we do will prevent you from using UrlKit in the future, so you should be able to use it in your applications without worry.
Adding Deep Linking To Your Application
Adding support for Deep Linking to your Flex 3.0 Application is relatively simple. The basic steps are:
- turn off the HistoryManager (the HistoryManager also uses the BrowserManager and will interfere with your fragment processing)
- initialize the BrowserManager with a default fragment and title
- determine when you want to update the fragment and call BrowserManager.setFragment.
- listen for events from the BrowserManager
- when you receive an event, interpret the fragment and change your application accordingly
If you use Flex Builder, you must choose one of the HTML templates that support the BrowserManager. Go to the project's Properties dialog, select "Flex Compiler" and ensure that the "Enable integration with browser navigation" is checked.
If you don't use Flex Builder, you should modify one of the index.template.html files in a template folder that ends with "with-history". You'll need to copy the support files in the subfolders as well.
Example
It might be easier to show the steps of adding deep linking in an example. Here's a "simple" MXML application. It has a TabNavigator with two panels/tabs and a checkbox on each panel. The application updates the fragment whenever the user switches tabs or checks/unchecks a checkbox, and remembers each of those changes in the browser history so you can use the back button to go back to previous states. You can bookmark any state and return to it and copy the URL of a state into a new browser and the application will appear to start in that state.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()" historyManagementEnabled="false">
<mx:Script>
<![CDATA[
import mx.events.BrowserChangeEvent;
import mx.managers.IBrowserManager;
import mx.managers.BrowserManager;
import mx.utils.URLUtil;
public var browserManager:IBrowserManager;
private function initApp():void
{
browserManager = BrowserManager.getInstance();
browserManager.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE, parseURL);
browserManager.init("", "Shipping");
}
private var parsing:Boolean = false;
private function parseURL(event:Event):void
{
parsing = true;
var o:Object = URLUtil.stringToObject(browserManager.fragment);
if (o.view == undefined)
o.view = 0;
tn.selectedIndex = o.view;
browserManager.setTitle((tn.selectedIndex == 0) ? "Shipping" : "Receiving");
tn.validateNow();
var details:Boolean = o.details == true;
if (tn.selectedIndex == 0)
shipDetails.selected = details;
else
recvDetails.selected = details;
parsing = false;
}
private function updateURL():void
{
if (!parsing)
callLater(actuallyUpdateURL);
}
private function actuallyUpdateURL():void
{
var o:Object = {};
var t:String = "";
if (tn.selectedIndex == 1)
{
t = "Receiving";
o.view = tn.selectedIndex;
if (recvDetails.selected)
o.details = true;
}
else
{
t = "Shipping";
o.view = tn.selectedIndex;
if (shipDetails.selected)
o.details = true;
}
var s:String = URLUtil.objectToString(o);
browserManager.setFragment(s);
browserManager.setTitle(t);
}
]]>
</mx:Script>
<mx:TabNavigator id="tn" change="updateURL()" width="300" >
<mx:Panel label="Shipping">
<mx:CheckBox id="shipDetails" label="Show Details" change="updateURL()" />
</mx:Panel>
<mx:Panel label="Receiving">
<mx:CheckBox id="recvDetails" label="Show Details" change="updateURL()" />
</mx:Panel>
</mx:TabNavigator>
</mx:Application>
The key pieces are:
- historyManagementEnabled="false" in the Application tag
- the BrowserManager.init() call in initApp() call that initializes the BrowserManager to a default fragment. "" means "nothing after the '#'".
- the three places where change="updateURL()" will update the URL based on changes to the TabNavigator or CheckBoxes.
- the BrowserManager.addEventListener() call in initApp() that listens for changes from the Browser
- the code in parseURL() that interprets the new fragment
This example uses URLUtils to take an object of property/value pairs, creates the fragment from it, and parses the fragment back into an object of property/value pairs. The object will have properties like view=1 and details=true, and create URL fragments like "#view=1;details=true". You can choose to not use URLUtils and create your own formatting and parsing if you wish.
If you use the third-party UrlKit, you have to set up a set of "rules" that map the components to the URL and vice-versa. It makes it easier to create more readable and hierarchical fragments like "#view=Shipping;details" or "#/Shipping/details=true". There's some extra work in figuring out the rules, but then you don't need to write methods like updateURL and parseURL as that functionality is contained in the rules.
Supported Browsers
The BrowserManager works by talking to Javascript in the HTML wrapper. Not every browser works the same way so there is Javascript for specific to each browser in the wrapper. Not every browser is supported. Right now we have tested support for:
- Internet Explorer 6 & 7 on Windows
- Firefox on Mac and Windows
- Safari on Mac
We've been unable to get good support in Opera for back button, and thus Opera is not officially supported. You can pass in a fragment on the URL and it will get passed to the BrowserManager correctly, however. We do not support any other browser not listed in this section.
Known Issues
- SDK-9722 DeepLinking behaves oddly when using named anchors in an HTML page.
- SDK-9632 Currently, DeepLinking support for Opera is limited. You can copy & paste an url into Opera and the application should start in the appropriate state, but the url is not subsequently updated with additional navigation.
- SDK-1902 After editing the application URL in the Firefox, subsequent user navigation will not automatically update the URL shown in Firefox. To update the URL, highlight the address bar in Firefox and press the esc key. The URL should update to the appropriate URL.
- SDK-11196 The text in historyFrame.html has extraneous debug text. This will be fixed in the next release.
- SDK-11033 The copyToAddressBar flag for the setFragment function will be removed in the next release.
- Setting the title in setFragment does not change the BrowserManager title property
- The API documentation for IBrowserManager is missing. Refer to the mx.managers.BrowserManagerImpl documentation for now.
