Futurescale, Inc. PureMVC Home

The PureMVC Framework Code at the Speed of Thought


Over 10 years of community discussion and knowledge are maintained here as a read-only archive.

New discussions should be taken up in issues on the appropriate projects at https://github.com/PureMVC

Pages: [1]
Print
Author Topic: SWFAddress and StateMachine  (Read 11509 times)
adrianw
Newbie
*
Posts: 8


View Profile Email
« on: November 25, 2009, 01:32:43 »

Hi all,

In my project I use StateMachine utility for managing my app's states. I would like to add deep linking functionality and here are my questions:

- How to use these 2 utils properly in PureMVC architecture? Create BrowserProxy and listen to SWFAddress events and update app state based on actual fragment?

-How about "internal" change? Listen for entering notifications for each state and apply fragment? Or first change url, and then change the state?

Thanks in advance
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #1 on: November 25, 2009, 08:20:50 »

There are lots of ways to handle it depending on how big your application is, whether it's modular, what the rest of your state machine looks like, etc.

You can have a look at the original Sea of Arrows Player codebase1. It uses StateMachine, and does deeplinking (albeit using Flex's BrowserManager instead of SWFAddress).

There, I pass through a number of states as the application starts up. Then I finally enter the 'Navigating' state. At that time, I get the BrowserManager and check to see if we have navigation on the URL. So when a playlist and/or track is passed in on the URL2, I populate a navigation object and send it down the pipeline to the PlaylistModule.

I also generate navigation from within the app, which bubbles up to the Shell and is placed on the URL so you can bookmark any place you navigate to in the app.

Note that the StateMachine in the SOA player is for the Shell, and mostly coordinates startup. When we hit the Navigating state, we stay there forever. This may or may not be appropriate for your FSM.

-=Cliff>

1 Sea of Arrows source: http://seaofarrows.com/srcview
2 An example deeplink on Sea of Arrows Player: http://seaofarrows.com/#P004/T040
3 The FSM for the Sea of Arrows Player: http://puremvc.tv/#P004/T435
Logged
adrianw
Newbie
*
Posts: 8


View Profile Email
« Reply #2 on: November 26, 2009, 01:12:15 »

Thank you for reply. I'll check Sea of Arrows for a useful hints :)
Logged
honi
Newbie
*
Posts: 5


Flash Developer

 - decimealgo@gmail.com
View Profile WWW Email
« Reply #3 on: December 01, 2009, 08:34:19 »

What I have been doing lately is to tie all my navigation logic to SWFAddress.
All navigation events come from the URL changing in first place.
Every component that can navigate to another section/state/etc does so by changing the URL, not sending a navigation event.
This way I only have one entry point for a navigation request: SWFAddress. It doesn't matter if the request initiated by the user clicking on the back button of the browser or by clicking on a button inside of the app.
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #4 on: December 02, 2009, 09:30:18 »

You have 4 issues:
1) Decoding the URL into actionable navigation information
2) Distributing the navigation information from the URL to the app,
3) Navigating internally to the new URL and setting the browser title.
4) Encoding a new URL when internal navigation is generated.

You don't want code all over your app to have knowledge about how to parse and set the URL. This means if you change the way the URL works to accommodate new information or structure, you have lots of code to change. In a Standard version app, you can just send notes to or from a mediator that talks to SWFAddress (or Flex's built in BrowserManager), but you still need a way of passing navigation information (which may potentially be more complex than goto state x)

But in a MultiCore application, you need to be able to pass the navigation information to multiple cores to get their parts of the overall navigation response completed.

For instance http://seaofarrows.com/#P004/T040 encodes a playlist and a track to play. By having my mediator take the URL and turn it into a formal navigation object, I can pass that navigation object to the PlaylistModule, which merely reads the track and playlist properties. Note that the track title isn't part of the URL. But once the app has navigated to it the browser title changes. That's because once the navigation was done and the data about the track retrieved from the server, another navigation object is bubbled back up to the Shell where the URL is found to be the same as the current location, so the browser title is just set.

And when you chose another track, all the module has to do is set the playlist, track and title on a new navigation object and send it off to the Shell.

The Navigation object is smart and it handles all the conversion and keeps track of whether a navigation was internally generated or external as well as letting you write the data and have it transformed into a URL (and set the browser title) or visa versa.

:
/*
  Sea of Arrows Music Player
  Site Design by Futurescale, Inc.
  Copyright(c) 2009 Cliff Hall <cliff@futurescale.com>
 */
package com.seaofarrows.musicplayer.common.model.vo
{
    /**
     * Navigation value object.
     * <P>
     * Used to describe a target state for application navigation.</P>
     */
    public class NavigationVO
    {
       
        public static const  EXTERNAL:String = "external";
        public static const  INTERNAL:String = "internal";

        /**
         * Constructor.
         */ 
        public function NavigationVO( source:String,
                                      playlist:String=null,
                                      track:String=null,
                                      elements:Array=null,
                                      params:Object=null,
                                      path:String=null,
                                      query:String=null,
                                      title:String=null )
        {
            this.source=source;
            if (playlist != null) this.playlist=playlist;
            if (track != null) this.track=track;
            if (elements != null) this.elements=elements;
            if (params != null) this.params = params;
            if (path != null) this.path=path;
            if (query != null) this.query=query;
            if (title != null) this.title=title;
        }

        /**
         * Set the source of the navigational directive.
         * <P>
         * Must be set to one of NavigationVO.EXTERNAL or NavigationVO.INTERNAL.</P>
         *
         * @param navSource the navigation source
         */
        public function set source( navSource:String ):void
        {
            if (navSource != EXTERNAL && navSource != INTERNAL) throw new Error("Bad navigation source");
            this.navigationSource = navSource;
        }
       
        /**
         * Get the navigation source.
         */
        public function get source():String
        {
            return navigationSource;
        }
   
        // The location title
        public var title:String;
       
        /**
         * Set the path, parsing from it the playlist and track.
         * <P>
         * The path is the navigational path within
         * the application, not a server path. On the
         * browser URL, path appears after the '#' and
         * before the '?this=that' query string.</P>
         * <P>
         * The first node of the path is the playlist, the
         * second node is the track, and the rest
         * are the nodes that can be used for later
         * expansion, such as selecting a preview or the
         * full track, or perhaps high or low bitrate.</P>
         * <P>
         * To parse the playlist, track and and the elements,
         * set the path, then read the action and the elements.</P>
         *
         * @params path the navigational path
         */
        public function set path( path:String ):void
        {
            elementList=path.split("/");
            if (elementList[0]=='') elementList.shift();
            this.playlist = elementList.shift();
            this.track = elementList.shift();
           
        }

        /**
         * Get the path.
         */
        public function get path():String
        {
            return this.playlist + "/" + this.track;
        }
       
        /**
         * The playlist.
         */
        private var playlistID:String;
        public function get playlist():String
        {
            return (playlistID != null)?playlistID:"";
        }
        public function set playlist(pid:String):void
        {
            playlistID=pid;
        }

        /**
         * The track.
         */
        private var trackID:String;
        public function get track():String
        {
            return (trackID != null)?trackID:"";
        }
        public function set track(tid:String):void
        {
            trackID=tid;
        }



        /**
         * Set the elements.
         *
         * @params elementList An array of strings in order to be joined with the playlist and track to form the path.
         */
        public function set elements( elementList:Array ):void
        {
            this.elementList = elementList;
        }

        /**
         * Get the element list.
         * <P>
         * An array of path elements, not including the action.</P>
         */
        public function get elements():Array
        {
            return elementList;
        }       
               
        /**
         * Set the parameters object.
         * <P>
         * This is an object with properties and values
         * to be combined into the query.</P>
         */
        public function set params( paramsMap:Object ):void
        {
            this.paramsMap = paramsMap;
        }

        /**
         * Get the parameters.
         * <P>
         * An object of parameters as properties/values.</P>
         */
        public function get params():Object
        {
            return paramsMap;
        }       
               
        /**
         * Set the query.
         * <P>
         * This should be a standard querystring,
         * that is, a list of key/value pairs.
         * key and value of a pair should be separated by
         * an '=' character, and the pairs should be
         * separated by ampersands.
         *
         * @params query the querystring
         */
        public function set query( query:String ):void
        {
            paramsMap={};
            var pairs:Array = query.split('&');
            var key:String;
            var value:String;
            var halves:Array;
            var names:Array = [];
            for (var i:int=0;i<pairs.length;i++)
            {
                halves=String(pairs[i]).split('=');
                if (halves[0]!='') paramsMap[halves[0]] = halves[1];
            }
        }

        /**
         * Get the query.
         */
        public function get query():String
        {
            var pairs:Array = [];
            for ( var paramName:* in paramsMap )
            {
                pairs.push(String(paramName) + "=" + paramsMap[String(paramName)])
            }
            return (pairs.length > 0)?pairs.join('&'):"";
        }

        /**
         * Get the fragment.
         *
         */
        public function get fragment():String
        {
            var halves:Array = [path,query];
            return (query != "")?halves.join('?'):path;       
        }

        /**
         * Set the fragment.
         */
        public function set fragment( frag:String ):void
        {
            var halves:Array = frag.split('?');
            path = halves[0];
            if (halves.length == 2) query = halves[1];             
        }

        /**
         * VO props concatenated as a human readable string.
         */
        public function toString():String
        {
            return "  Source [" + source + "]\n" +
                   " Fragment[" + fragment + "]\n" +
                   "    Path [" + path + "]\n" +
                   "   Query [" + query + "]\n" +
                   "Playlist [" + playlist + "]\n"+
                   "   Track [" + track + "]\n"+
                   "Elements [" + elementList.join(' * ') + "]\n"+
                   "   Title [" + title + "]\n";
        }
       
        // The source of the navigational directive (internal or external)
        protected var navigationSource:String;
       
        // The elements of the navigational path
        protected var elementList:Array = [];

        // The parameters of the query
        protected var paramsMap:Object = {};

    }
}

-=Cliff>

Logged
honi
Newbie
*
Posts: 5


Flash Developer

 - decimealgo@gmail.com
View Profile WWW Email
« Reply #5 on: December 02, 2009, 10:47:55 »

After posting I kept reading the forum looking for info on this subject and I ended up looking at the sea of arrows source, and the NavigationVO.
By re-reading my other post it sounds as if a Mediator would send an URL, which isn't a wise decision as you've already showed.
Anyways, I am actually implementing some type of NavigationVO.

The inconvenience I have found is the following:
How could I map Mediators to specific URLs, or better said, a NavigationVO.
Should there be a centralized place where every NavigationVO is received and consecuently forward to a concrete Mediator?
Should every mediator listen for NavigationVO's and decide on their own if they should use it or ignore it?

I like the idea of an URL-to-Mediator mapping Proxy.
Lets say we can call a method on this Proxy and map an URL (string) to a concrete Mediator. We would use Mediator.NAME to reference the Mediator.
When a NavigationVO is received by some type of NavigationMediator, it would consult the Proxy to unmap the url and return a Mediator. In consecuence, notify that mediator of the Navigation request.
The mapping would be one to one, or one to many. The same URL could be mapped to many mediators.

I this this woudl be useful in situations where the app is a site, and the URL not only represents the site's sections, but possibly some type of subsection, or product.
For example: www.mysite.com/#/catalog/category/product/
This NavigationVO would trigger a section change (if not already in section catalog). And it could also trigger the product info and image loading process started by a ProductViewerMediator.

I'm constantly implementing this type of navigation lately and I'm thinking of building some type of utility which I can re use. Any suggestions?
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #6 on: December 02, 2009, 02:16:15 »

In the Sea of Arrows source also check the BrowserManagerMediator in the Shell module. That is the single point of contact with the browser mechanism. Using swfaddress, this would be a SwfAddressMediator.

But inside the app, yopu don't want to be mapping mediators to urls. They should respond to notes that make sense within the context of your app, such as 'SHOW_PRODUCTS'.

Nor should every mediator listen for a navigation note and decide whether to act.

Commands are the place to put your business logic. A single navigation might require a lot of logic and notifications to respond to. Put the logic of responding in a command, but not the url decoding/encoding; keep that in the vo so that props can be set by any actor and when the browser mediator gets it its immediately usable and visa versa.

-=Cliff>
Logged
honi
Newbie
*
Posts: 5


Flash Developer

 - decimealgo@gmail.com
View Profile WWW Email
« Reply #7 on: December 02, 2009, 02:42:19 »

I've looked a bit more in the SOA src.
It makes sense not to map an url directly to a mediator, but rather have somebody translate the url to some logical action inside the context of the app.
Where should this translation be done? In the BrowserMediator?
Or should the BrowserMediator send a NAVIGATION_REQUEST that executes a NavigationCommand which in turn receives the NavigationVO and acording to it's data decide what note should be send?
I think the second option is the way to go. I think SOA is doing something like that, but a bit more complex since it's multicore.
This leaves me thinking though.. Which elements would re usable? BrowserMediator?
NavigationCommand and NavigationVO will always be specific to the app.
I think this isn't pointing to an utility but rather to a reusable class for handling SWFAddress in PureMVC projects.
Logged
Pages: [1]
Print