Using the BrowserManager

The BrowserManager is a Singleton class that acts as a proxy between the browser and the application. It provides access to the URL in the browser address bar similar to accessing the document.location property in JavaScript. When the URL changes in the browser, the BrowserManager is notified of the event. You can then change the URL, respond to the event, or block the event.

To get a reference to the BrowserManager, you call its getInstance() method. This method returns the current instance of the manager, which implements IBrowserManager. You can then call methods on the manager such as setTitle() and setFragment().

You can also listen to events on that manager. The events are browserURLChange, urlChange, and applicationURLChange. These events are described in About BrowserManager events.

You can also access properties of the manager. These properties store the browser’s current title, plus the full URL and its sub-parts, fragment and base. The properties are read-only, but you can use methods of the manager, such as setTitle() and setFragment(), to set the values of some of them.

You typically call the getInstance() method when your application initializes. This returns an instance of the Singleton BrowserManager. You then register an event listener for the browserURLChange event. This event is dispatched when the user clicks the Back or Forward button in the browser. Finally, you call the init() method to initialize the BrowserManager. The first parameter of the init() method defines the default fragment, and the second parameter defines the title for the current page.

The following example instantiates the BrowserManager, registers the parseURL() method to listen for browserURLChange events, and calls the init() method with a blank fragment as the default fragment. It sets the value of the page’s title to “Test Deep Linking”:

private function initApp():void { 
    browserManager = BrowserManager.getInstance(); 
    browserManager.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE, parseURL); 
    browserManager.init("", "Test Deep Linking"); 
}

Calling the BrowserManager class’s init() method also gets the current URL as a property in the BrowserManager.

If you set a value for the default fragment in the init() method, when the URL changes, the URL with the default fragment will be entered in the browser’s history. This way, if the user clicks the Back button or in some other way accesses this page from the browser’s history, this fragment will be used. Also, the BrowserManager returns the default fragment if there is nothing after the pound sign (“#”) in the URL.

Updating the URL

You use the BrowserManager’s setFragment() method to update the URL in the browser’s address bar. You can only change the fragments in the URL. You cannot change the base of the URL, including the server, protocol, or port numbers.

When you use the setFragment() method to change the URL, you trigger an applicationURLChange event.

The following example updates the URL in the browser whenever you change the active panel in the TabNavigator container. It also keeps a record of the current URL and previous URL each time an applicationURLChange event is triggered.

<?xml version="1.0" encoding="utf-8"?>
<!-- deeplinking/UpdateURLExample.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="init();">   

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
    <![CDATA[
        import mx.events.BrowserChangeEvent;
        import mx.managers.IBrowserManager;
        import mx.managers.BrowserManager;
        import mx.utils.URLUtil;

        private var browserManager:IBrowserManager;

        private function init():void {
            browserManager = BrowserManager.getInstance();
            browserManager.addEventListener(BrowserChangeEvent.APPLICATION_URL_CHANGE, 
                logURLChange);
            browserManager.init("", "Welcome!");
        }

        public function updateTitle(e:Event):void {
            browserManager.setTitle("Welcome " + ti1.text + " from " + ti2.text + "!");
        }

        private function updateURL(event:Event):void {
            var s:String = "panel=" + event.currentTarget.selectedIndex;
            browserManager.setFragment(s);
        }

        private function logURLChange(event:BrowserChangeEvent):void {
            ta1.text += "APPLICATION_URL_CHANGE event:\n";
            ta1.text += " url: " + event.url + "\n"; // Current URL in the browser.
            ta1.text += " prev: " + event.lastURL + "\n"; // Previous URL.
        }
    ]]>
    </fx:Script>

    <mx:TabNavigator id="tn" width="300" change="updateURL(event)">
            <mx:Panel label="Personal Data">
                <mx:Form>
                    <mx:FormItem label="Name:">
                        <mx:TextInput id="ti1"/>
                    </mx:FormItem>
                    <mx:FormItem label="Hometown:">
                        <mx:TextInput id="ti2"/>
                    </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>
                            <mx:dataProvider>
                                <fx:String>Visa</fx:String>
                                <fx:String>MasterCard</fx:String>
                                <fx:String>American Express</fx:String>
                            </mx:dataProvider>
                        </mx:ComboBox>
                    </mx:FormItem>
                    <mx:FormItem label="Number:">
                        <mx:TextInput id="ccnumber"/>
                    </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 label="Agree"/>
            </mx:Panel>            
    </mx:TabNavigator>
    <mx:TextArea id="ta1" width="580" height="400"/>

</s:Application>

When you click on the second panel, you trigger a change event, which causes Flash Player to call the updateURL() method. This method calls the setFragment() method that changes the URL in the browser’s address bar. If you cycle through the panels, the URL in the browser’s address bar will change from this:

http://localhost:8100/devapps/code/deeplinking/FragmentExample.html#panel=1

To this:

http://localhost:8100/devapps/code/deeplinking/FragmentExample.html#panel=2

And then, to this:

http://localhost:8100/devapps/code/deeplinking/FragmentExample.html#panel=0

Each time the application changes the URL in the browser’s address bar, Flash Player dispatches an applicationURLChange event. This example logs the previous and current URLs, which are properties of this event object.

Parsing the URL

Changing the URL in the browser’s address bar does not necessarily provide you with deep linking functionality. For example, loading a bookmarked URL that specifies the panel would not start the application on that panel. You must add code to the application that parses the URL so that the application’s state reflects the URL.

In this case, you add a listener for the browserURLChange event. In that listener, you parse the URL and set the application state according to the results. For example, if the URL includes a fragment such as panel=2, then you write code that sets the current panel with the selected index of 2.

The browserURLChange event is not triggered when the application first loads. As a result, you must also check the URL on startup with the application’s creationComplete event, and trigger the parsing before the application finishes being rendered on the screen.

The following is a simple example that sets the value of the Accordion’s selectedIndex property to the value of the index fragment in the URL. You request this application by setting the value of the index fragment on the URL, as the following example shows:

http://www.myurl.com/InitFrag.html#index=1

When the application starts up, the Accordion is opened to the panel corresponding to the value of the index fragment:

<?xml version="1.0" encoding="utf-8"?>
<!-- deeplinking/InitFrag.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="init(event);">    

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
            import mx.managers.BrowserManager;
            import mx.managers.IBrowserManager;
            import mx.events.BrowserChangeEvent;
            import mx.utils.URLUtil;

            private var bm:IBrowserManager;            
            private function init(e:Event):void {
                bm = BrowserManager.getInstance();                
                bm.init("", "Welcome!");
                parseURL(e);
            }
        
            [Bindable]
            private var indexFromURL:int;
            private function parseURL(e:Event):void {            
                var o:Object = URLUtil.stringToObject(bm.fragment);                
                indexFromURL = o.index;
            }           
        ]]>
    </fx:Script>

    <mx:Accordion selectedIndex="{indexFromURL}">
        <mx:VBox label="Panel 1">
            <mx:Label text="Accordion container panel 1"/>
        </mx:VBox>
        <mx:VBox label="Panel 2">
            <mx:Label text="Accordion container panel 2"/>
        </mx:VBox>
        <mx:VBox label="Panel 3">
            <mx:Label text="Accordion container panel 3"/>
        </mx:VBox>    
    </mx:Accordion>   

</s:Application>

The following example expands on the example from Updating the URL. It reads the URL on startup, and opens the application to the first, second, or third panel, depending on the value of the panel fragment. It also sets the title of the HTML page according to which panel is opened.

<?xml version="1.0" encoding="utf-8"?>
<!-- deeplinking/TabNavExample.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="initApp()" 
    height="250" 
    width="500">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx: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);
        }
    ]]>
    </fx: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>

</s:Application>

This example has one major drawback: it does not “remember” the state of the panel that is not in the current view. If you copy the bookmark and open it in a new browser, the view that you were last looking at, and the state of the CheckBox on that view, are maintained. However, the CheckBox in the other view that was hidden is reset to its original value (unchecked). There are several techniques to solve this issue. For more information, see Using deep linking with navigator containers.

About BrowserManager events

The BrowserManager triggers the following types of BrowserChangeEvent events:

  • applicationURLChange

  • browserURLChange

  • urlChange

These changes can be triggered by the following actions:

  • URL is changed programmatically, such as with the BrowserManager’s setFragment() method (applicationURLChange)

  • User clicks the Forward or Back button (browserURLChange)

  • User changes the URL and clicks Enter or Go (browserURLChange)

The urlChange event is triggered whenever either applicationURLChange or browserURLChange events are triggered.

The following example shows the properties of the change events in a DataGrid control. You can trigger an applicationURLChange event by selecting a new value in the ComboBox control. You can trigger a browserURLChange event by using the Forward and Back buttons in your browser. In both cases, you also trigger a urlChange event.

<?xml version="1.0" encoding="utf-8"?>
<!-- deeplinking/URLChangeLogger.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="init();">   

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
    <![CDATA[
        import mx.managers.BrowserManager;
        import mx.managers.IBrowserManager;
        import mx.events.BrowserChangeEvent;

        private var bm:IBrowserManager;

        private function init():void {
            bm = BrowserManager.getInstance();

            bm.addEventListener(BrowserChangeEvent.APPLICATION_URL_CHANGE, doEvent);
            bm.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE, doEvent);
            bm.addEventListener(BrowserChangeEvent.URL_CHANGE, doEvent);

            bm.init("", "Base title");
        }

        public function doEvent(evt:BrowserChangeEvent):void {
            eventDG.dataProvider.addItem(evt);
        }
    ]]>
    </fx:Script>
    
    <fx:Declarations>
        <fx:Array id="dp">
            <fx:Object label="one"/>
            <fx:Object label="two"/>
            <fx:Object label="three"/>
        </fx:Array>
    </fx:Declarations>

    <mx:ComboBox id="cb" dataProvider="{dp}" 
        change="bm.setFragment('selectedItem=' + cb.selectedItem.label);"/>
    
    <mx:DataGrid id="eventDG" 
        dataProvider="[]" 
        width="100%" 
        variableRowHeight="true" 
        wordWrap="true" 
        height="500"/>
</s:Application>

Changing the URL fragments in the browser’s address bar triggers a browserURLChange event but does not trigger a page reload in FireFox or Internet Explorer 7. In Internet Explorer version 6 and earlier, changing the part of the address that is to the right of the pound sign (“#”) does trigger a page reload.