注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

神魔破杜梓的叨叨堂

Programming every day!

 
 
 

日志

 
 
 
 

如何在一个基于MVC模式的Flex应用程序中实现History Manager  

2008-08-06 13:19:11|  分类: My Tech |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
原文来自Flex cookbook beta

Problem Summary

In this simple article we’ll explore how simple it is to implement History Manager functionality in a MVC Flex application and the basic rules of mvc pattern architecture.

Solution Summary

We will use an mvc design pattern to keep the graphic views separated from the data that will store the current state of ours application.

Explanation

How to implement History Manager in a MVC Flex Application

[Gallo_Teo] - Matteo Lanzi

In this simple article we’ll explore how simple it is to implement History Manager functionality in a MVC Flex application and the basic rules of mvc pattern architecture.

I’ll use Adobe Cairngorm as micro architecture  but  the concept is the same if you want to implement it with pureMVC.

 a working example can be found here

Application Overview

The test application is very simple; it’s a normal image gallery that takes images from the server and displays them in “Tile View” or in “Detail View”.
The target of this tutorial is to catch a good application strategy to implement easily the History manager, so that the user will be able to go back and forward in his navigation through the buttons of his browser

Therefore, the steps to create this simple application are:

1.       Create a Business Class that has to request the server ( ImageDelegate.as )

2.       Create a command, so that the application can query the server ( GetImageCommand.as )

3.       Create a Model where the command will store the images gotten from the server ( Model.as )

4.       Create the view interface to display the result

5.       Implement the History Manager Strategy.

To reflect the typical cairngorm mvc architecture I’ve created this project structure.

That’s very simple but very clean, you can reuse it for a lot of projects.

 

1  - Business and delegates

Under the business package we will find all the classes that communicate with the server, with audio stream, video stream, file stream, xml and so on. All the requests that are Asynchronies are part of the business package.

If this is the first time you’ve been hearing about “mvc” please refer to Steven Webster articles on Adobe web site for more detailed information about mvc and cairngorm.
http://www.adobe.com/devnet/flex/articles/cairngorm_pt1.html

 

However  don’t worry, these concepts are very simple and will be more clear in the next section

Oue ProducDelegate class has to request the server the list of images, and can implement also a lot of functionality such as resize image request, a specific image url and so on..
It has to communicate with the server side services.

In our case we really don’t need to do that so we can implement it as a fake service in this way

public class ImageDelegate

      {

           

            protected var responder : IResponder ;

           

            public function ImageDelegate( responder : IResponder)

            {

                  this.responder = responder ;

            }

           

           

            /**

             *

             * @ getImages

             * get all Images from the server

             * now it is a fake for the tutorial

             * */

            public function getImages ( ) :void{

                 

                  //-- create a fake image list

                  var list : Array = new Array () ;

                  for ( var i : int = 0; i< 14 ; i++ ){

                       

                        var image : ImageVo = new ImageVo () ;

                        image.id = "id"+ i ;

                        image.imagePreview = "./data/" + i.toString() + ".jpg";

                        list.push( image ) ;

                  }

                 

                  responder.result( list ) ;

                 

            }

           

           

            /**

             *

             * example

             * */

            public function getImagePreview () : void {

                 

            }

            //   ......

           

            /**

             *

             * delegate store all the functions about a remote

             * service... this is the example : )

             *

             * so the commanda can reuse this in different way

             * depending from the context

             * */

            public function resizeImage():void {     }

}

2 – Commands

The MVC architecture is very intuitive; the concept is: “ Does our application need something?” OK ! it has to give a “Command”.

Think about it, does the user click a button to view a product ? Will our button dispatch something like “ViewProductCommand”, or does the user want to search something ? The application will dispatch something like “getSearchedProductCommand”. Our case is easier and we only need to get a list of images so we will have a command “GetImagesCommand”.

 

In order to do that we will implements a Cairngorm class that’s “ICommand” which simply has one method called execute.

 

public class GetImagesCommand implements ICommand, IResponder

      {

            protected var model : Model ;

 

            public function execute(event:CairngormEvent):void

            {

                  model = Model.instance ;

                  new ImageDelegate( this ).getImages() ;

            }

In the “execute” method we call “getImages” function of ImageDelegate class (?)
I think now you can imagine the importance of the delegate: we can have a single delegate for all the services and a lot of different commands for a specific use.

As I said before the request to the server is asynchronous, so a delegate has to know which command to ask for a service and give it back to the answerer.
It’s for this reason that in the command class we implement IResponder interface too; this will allow the answerer to delegate to the correct command.
In case we don’t use delegate, our command could die after the execute function because no object refers to it.

Here is the command

new ImageDelegate( this )

and in delegate class when it receives the answerer it does

responder.result( list ) ;

this is the complete GetImagesCommandCode; you can see that , by implementing IResponder interface, we have two method ( fault and result ) that are the delegate answerer

 

public class GetImagesCommand implements ICommand, IResponder

{

      protected var model : Model ;

 

      public function execute(event:CairngormEvent):void

      {

            model = Model.instance ;

            new ImageDelegate( this ).getImages() ;

      }

     

     

      /**

       *

       * @result

       * occurs when the delegate succefully

       * query the server

       * */

      public function result(data:Object):void

      {

            var list : Array = data as Array ;

            if ( ! list ){

                  fault( "no array" ) ;

                  return ;   

            }

           

            model.images = new ArrayCollection ( list ) ;

            if ( list.length > 0 )

                  model.currentItem = model.images.getItemAt( 0 ) as ImageVo ;

      }

     

     

      /**

       *

       * @fault

       * occurs when some problems occur

       * */

      public function fault(info:Object):void

      {

            trace ( getQualifiedClassName( this ) + " ERROR " + info ) ;

      }

     

}

 

Your question now could be “How can our application intercept commands and execute those?
It’s very simple because we have the Controller class that has to route our commands.
You have to think about the controller as an “old style call center” where you call, you want to speak to someone connecting people.

 

In order to do that in controller class, you simply have to register your command and specify “when” it has to been executed.

 

In our case :

      addCommand( ApplivationEvent.GET_IMAGES, GetImagesCommand ) ;

 

so we say when an Application.GET_IMAGES event occurs, please execute the “GetImagesCommand”

 

I don’t want to go on talking about mvc because it’ not the primary target of this article but I now think you should have a more complete idea about how Model View Controller works.

 

 

3 – The Model – store application data

The model is the most stupid part of an MVC application. It has not to “think” but only to store our data. If the primary concept of MVC programming is to keep in 3 separated layers our application, the model part allows to store our data as a bank does, and to share it with all the application’s part.

Let’s take this example:

When the application is loaded, it dispatches an event to get “getImagesCommand”. After the command is executed, it has to store the answerer somewhere… that place is the model.

Through flex binding, or for pureMVC through Notifications, all the graphic interfaces (called views) will update themselves to reflect model changes.

 

So the workflow is

-          User clicks something and generates an event (like Application.GET_IMAGES )

-          Controller is listening to the events and calls the command

-          The command is executed

-          The command stores the data to the model

-          All the views update themselves to reflect the model changes

 

Quite simple, isn’t it?

Ok, let’s go back to our application; we will first store three variables: “currentView” that stores the kind of view ( Tile View or Detail view )  , images ( that’s the list saved by getImagesCommand ) and “currentItem” ( the current image selected from the user

 

For the moment, in the model we could declare them like this

 

public var currentItem : ImageVo

public var images: ArrayCollection;

public var currentView : String ;

 

 

 

4 – View – display results

Once we have the image list stored into the model is very simple to create a user interface to display it; in few lines we can create a TileList View

<?xml version="1.0" encoding="utf-8"?>

<mx:Canvas

      xmlns:mx="http://www.adobe.com/2006/mxml"

      width="400"

      height="300"

      creationComplete="init()"

      >

      <mx:Script >

            <![CDATA[

                  import it.actionscript.vo.ImageVo;

                  import it.actionscript.model.Model;

                 

                  [Bindable]

                  protected var model : Model ;

                 

                  /************************************************************

                   *                           INIT

                   * *********************************************************/

                  private function init () : void {

                        model = Model.instance ;

                  }

                 

                 

            ]]>

      </mx:Script>

      <mx:TileList

            id="tile"

            width="100%"

            height="100%"

            columnCount="4"

            dataProvider="{model.images}"

            itemRenderer="it.actionscript.view.TileViewRenderer"

            change="{ model.currentItem = tile.selectedItem as ImageVo  }"

            selectedIndex="{ model.images.getItemIndex( model.currentItem )  }"

            selectionColor="0xAAEECC"

            />

</mx:Canvas>

 

And an itemRenderer for it

 

 

 

<?xml version="1.0" encoding="utf-8"?>

<mx:Canvas

      xmlns:mx="http://www.adobe.com/2006/mxml"

      width="400"

      height="300"

      creationComplete="init()"

       >

      <mx:Script>

            <![CDATA[

                  import mx.effects.Fade;

                  import mx.effects.Zoom;

                  import it.actionscript.vo.ImageVo;

                  import mx.controls.Image;

                       

                  [Bindable]

                  private var imageVo : ImageVo ;

                       

                  [Bindable]

                  public static var counter : Number = 0;

                       

                  [Bindable]

                  private var index : Number = 0;   

                       

                  /**

                   *

                   * init

                   * */

                  private function init () : void {

                  }

                 

                  /**

                   *

                   * @set data

                   * override the method data to store my kind

                   * of data, in this case ImageVo

                   * **/

                  override public function set data(value:Object):void {

                        super.data = value ;

                       

                        if( value is ImageVo )

                             imageVo = data as ImageVo ;       

                  }

                 

                 

                  private function imageLoaded ( ): void {

                        counter++

                        index = counter;

                        if ( ! image.visible )

                             myEffect.play();

                  }

            ]]>

      </mx:Script>

      <mx:Image

            id="image"

            width="100%"

            height="100%"

            autoLoad="true"

            maintainAspectRatio="true"

            verticalAlign="middle"

            horizontalAlign="center"

            source="{imageVo.imagePreview}"

            visible="false"

            complete="{imageLoaded()}"

            cacheAsBitmap="true"

           

            />

           

      <mx:Parallel id="myEffect" startDelay="{index * 200}" duration="500" target="{image}" >

            <mx:Fade alphaFrom="0" alphaTo="1"/>

            <mx:Zoom  zoomHeightFrom="0.1" zoomHeightTo="1" zoomWidthFrom="0.1" zoomWidthTo="1"/>

            <mx:SetPropertyAction name="visible" value="true" />

      </mx:Parallel>

</mx:Canvas>

 

 

 

 

Here you’ll understand the importance of this mvc architecture.

In the TileList tag you can notice

 

change="{ model.currentItem = tile.selectedItem as ImageVo  }"

 

this line means that when the user changes the image selection into the TileList, the selected image is stored into the model; so then, if he wants to display the detailed(?) view, it has to point to model.currentItem

 

That’s really important because your data is not linked to the view.

In a bad programming style you could have something like

 

parant.parent.tileList.selectedItem

 

Think now if your committer asks you to change the graphic… all your application can’t work anymore if you don’t respect that path.

For that reason, it is very important to implement a good mvc architecture.

 

You can see the code of the detail view in the attached source, you’ll find the same logic

 

5 – Add History Behavior

Now that our application is done, it is really simple to extend it, in order to have the browser history working.

Take a look at the Model class:

Here we stored the “currentItem” and the “currentView” variables. Now we want in the history of our browser these two variables to be stored, so that the user can navigate back through previous images and views.

 

Flex skd has an HistoryManger, it simply works with url and url’s variables

You can find more information about this class here

http://livedocs.adobe.com/flex/3/langref/index.html?mx/managers/HistoryManager.html&mx/managers/class-list.html

 

To pass the current application state to HistoryManger our application has to implement IHistoryManagerClient

This interface has two interesting methods called “loadState” and “saveState”…I the behavior it’s clear.

Imagine the situation of a typical application where, every view like the tile or the detail or the top bar to switch between views, should have to implement this interface….by the way, a very boring and dangerous work!!!

 

Our job is easier because our need is to catch the  “currentItem” selection changes, or “currentView” changes,  and we need to save this state to HistoryManager.

The best way to realize it is that “Model” class implement IHistoryMangerClient so we will add these two methods to the model.

 

/**

 *

 * HISTORY MANAGER

 * save the current state of the object

 * */

public function saveState():Object {

     

     

      var object : Object = new Object ();

      object.currentPage = currentView ;

      object.productId = currentItem  ? currentItem.id : "";

     

      return object ;

}

 

/**

 *

 * HISTORY MANAGER

 * load the state requested

 * */

 

public function loadState(state:Object):void {

      if( ! state )return ;

     

      var object : Object = state ;

     

      currentView = object.currentPage;

     

      //-- seach the product

      var id : String = object.productId.toString() ;

      if( id.length > 0 ) {

           

            for each ( var image : ImageVo in images ) {

                  if ( id == image.id ) {

                        currentItem = image ;

                        break ;

                  }

            }

      }

      else

            currentItem = null ;

}

 

In these two methods we store or we load the state of “currenItem” and “currentView”, and in our constructor we have to put

 

HistoryManager.register( this ) ;

 

to notify to HistoryManager that this model is a historyManagerClient

 

Ok, the last step is to save the state every time one of these two variables changes; in order to do it we can wrap the simple vars with getter and setter methods to get a property

 

 

/**

 *

 * @currentView

 * */

public function get currentView ( ) : String {

      return _currentView ;

}

public function set currentView ( value : String ) :void{

      if( _currentView == value )

            return ;

           

      _currentView = value

      HistoryManager.save();

}

 

/**

 *

 * @currentItem

 * */

public function get currentItem () : ImageVo {

      return _currentItem ;

}

public function set currentItem ( value : ImageVo ) : void {

      if (( _currentItem == value ) || ( ! value ))

            return ;

           

      _currentItem = value ;

      HistoryManager.save() ;

     

}

 

 

Therefore, every time someone tries to change these variables, the value is called “HistoryManager.save()”. It will automatically call the saveState method of ours IHistoryManagerClientInterface

 

 

 

Conclusion

In this article we discovered how simple is to structure our application in MVC way; advantages keeping three abstraction layers are clear and our software can be well managed and expanded for eventual changes.
Application core is indeed the Model; it is the “bank” of our data and its changes are easily reflected by the views through data Binding. For this reason, and only with the use of such a simple structure, it’s really easy and quick to implement an History patter to our Application: we only need to save our preferred state of the model and load them when needed.
In order to do that, we simply use a manger called “HistoryManager” and our Model has to implement its client interface.

That’s all .

 

Related files for download


As.it-MVC-History-Manager.zip
  评论这张
 
阅读(391)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017