A common use case is for a user to start filling out a form, and then either bookmark the form for later use, or send the URL to someone else to take a look at the values. In many cases, forms are implemented as navigator containers such as Tab Navigator and Accordion containers.
This can be a problem for deep linking because you might try to read properties on controls that are in views that have not yet been viewed (and therefore, the controls have not yet been instantiated). One possible solution is to disable deferred instantiation, and set the value of the container's creationPolicy property to all. This instructs Flex to instantiate all controls in all views at application startup, regardless of whether they can be viewed initially or not. This is not a recommended solution because it creates additional overhead when the application starts, and can end up using unnecessary amounts of processor time and memory. Depending on the complexity of the user interface, setting the value of the creationPolicy property to all can seriously degrade performance of your Flex application and detract from a positive user experience. For more information about creation policies, see About the creationPolicy property.
A technique that does not rely on deferred instantiation is to bind the values of the properties you want to maintain state on to the values in the URL. Controls that have not yet been created apply these values when they are created. This has the benefit of keeping the URL and the application's state in sync with each other, without requiring that a view's controls are first created.
You do this by setting up variables that are mapped to the URL's values at start up and then kept in sync with the application as the user interacts with it. When the application starts up, you set the value of the bound property to the value taken from the URL (if there is one). Then, whenever the application's state changes (such as when the user navigates to a new panel or clicks on a check box), you update the bound property to the new value. You also update the value of the property's fragment in the URL.
By doing this, though, you must wrap your variable assignments with try/catch blocks because you will attempt to set the values of variables with properties of components that have not yet been instantiated. This ensures that assignment errors are caught rather than thrown as run-time errors.
The following example uses a Tab Navigator container to help the user through a payment process. Whenever the user changes the panel, changes the value in a TextInput control, or checks the CheckBox control, the URL and the bound properties are updated. At any time, you can bookmark the URL, close your browser, and then come back to the application at a later time. The values of all the properties are maintained in the bookmarked URL.
<?xml version="1.0" encoding="utf-8"?>
<!-- deeplinking/ComplexMultiPanelExample.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
historyManagementEnabled="false"
creationComplete="init();parseURL(event)"
>
<mx:Script>
<![CDATA[
import mx.events.BrowserChangeEvent;
import mx.managers.IBrowserManager;
import mx.managers.BrowserManager;
import mx.utils.URLUtil;
private var bm:IBrowserManager;
[Bindable]
private var agreeBoxFromURL:Boolean;
[Bindable]
private var personNameFromURL:String;
[Bindable]
private var hometownFromURL:String;
[Bindable]
private var cctypeFromURL:int;
[Bindable]
private var ccnumberFromURL:String;
private function init():void {
bm = BrowserManager.getInstance();
bm.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE, parseURL);
bm.init("", "Welcome!");
}
/* This method is called once when application starts up. It is also
called when the browser's address bar changes, either due to user action
or user navigation with the browser's Forward and Back buttons. */
private function parseURL(event:Event):void {
var o:Object = URLUtil.stringToObject(bm.fragment, "&");
if (o.panel == undefined)
o.panel = 0;
tn.selectedIndex = o.panel;
tn.validateNow();
personNameFromURL = o.personName;
hometownFromURL = o.hometown;
ccnumberFromURL = o.ccnumber;
cctypeFromURL = o.cctype;
agreeBoxFromURL = o.agreeBox;
}
public function updateTitle(e:Event):void {
l1.text += "updateTitle()\n";
bm.setTitle("Welcome " + personName.text + " from " + hometown.text + "!");
}
private function updateURL():void {
/* Called when state changes in the application, such as when the panel changes,
or a checkbox is checked.
You must wrap the following assignments in a try/catch block, otherwise the
application tries to access components that have not yet been created.
You can circumvent this by setting the container's creationPolicy to "all",
but that is not a good solution for performance reasons. */
try {
personNameFromURL = personName.text;
hometownFromURL = hometown.text;
ccnumberFromURL = ccnumber.text;
cctypeFromURL = cctype.selectedIndex;
agreeBoxFromURL = agreeBox.selected;
} catch (e:Error) {
}
var o:Object = {};
try {
o.panel = tn.selectedIndex;
o.personName = personName.text;
o.hometown = hometown.text;
o.ccnumber = ccnumber.text;
o.cctype = cctype.selectedIndex;
o.agreeBox = agreeBox.selected;
} catch (e:Error) {
} finally {
var s:String = URLUtil.objectToString(o, "&");
bm.setFragment(s);
}
}
]]>
</mx:Script>
<mx:TabNavigator id="tn" width="300" change="updateURL()">
<mx:Panel label="Personal Data">
<mx:Form>
<mx:FormItem label="Name:">
<mx:TextInput id="personName"
text="{personNameFromURL}"
focusOut="updateURL()"
enter="updateURL()"
/>
</mx:FormItem>
<mx:FormItem label="Hometown:">
<mx:TextInput id="hometown"
text="{hometownFromURL}"
focusOut="updateURL()"
enter="updateURL()"
/>
</mx:FormItem>
<mx:Button id="b1" click="updateTitle(event)" label="Submit"/>
</mx:Form>
</mx:Panel>
<mx:Panel label="Credit Card Info">
<mx:Form>
<mx:FormItem label="Type:">
<mx:ComboBox id="cctype"
change="updateURL()"
selectedIndex="{cctypeFromURL}"
>
<mx:dataProvider>
<mx:String>Visa</mx:String>
<mx:String>MasterCard</mx:String>
<mx:String>American Express</mx:String>
</mx:dataProvider>
</mx:ComboBox>
</mx:FormItem>
<mx:FormItem label="Number:">
<mx:TextInput id="ccnumber"
text="{ccnumberFromURL}"
focusOut="updateURL()"
enter="updateURL()"
/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
<mx:Panel label="Check Out">
<mx:TextArea id="ta2" text="You must agree to all the following conditions..."/>
<mx:CheckBox id="agreeBox"
label="Agree"
selected="{agreeBoxFromURL}"
click="updateURL()"
/>
</mx:Panel>
</mx:TabNavigator>
<mx:TextArea id="l1" height="400" width="300"/>
</mx:Application>
Rather than update the values of the bindable variables in the updateURL() method, you can also set their values in the event handlers. For example, when the user changes the value of the hometown TextInput control, you can use the focusOut and enter event handlers to set the value of the hometownFromURL property:
<mx:TextInput id="hometown"
text="{hometownFromURL}"
focusOut="hometownFromURL=hometown.text;updateURL()"
enter="hometownFromURL=hometown.text;updateURL()"
/>
In this example, the controls in the view update the model when the controls' properties change. This helps enforce a cleaner separation between the model and the view.