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

神魔破杜梓的叨叨堂

Programming every day!

 
 
 

日志

 
 
 
 

使用PureMVC创建flash站点  

2008-05-31 20:27:18|  分类: My Tech |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
原文来自hubflanger.com

At a recent FlashCodersNY meeting where the discussion centered around the subject of the MVC (Model-View-Controller) design pattern, it struck me that a lot of Flash developers, especially those coming from design backgrounds, did not see the value in using the MVC design pattern.

Well, I’m not here to make the case for using MVC but if you have spent enough time struggling with structuring an application so that it’s clearly represented by a set of well-defined relationships and responsibilities, you would’ve likely tried to implement some kind of MVC pattern. Now, some design pattern is certainly better none. Trouble is, every developer has his or her own approach to MVC. When a team of developers are working on the same project, things can get confusing pretty quickly if there are no common standards to adhere to.

This brings us to the PureMVC framework. Created by Cliff Hall, this framework provides a set of well-defined protocols for implementing the MVC design pattern with your application, rendering the grunt work of manually hooking up an MCV structure a thing of the past. However, great as PureMVC may be, it does not present an easy learning curve for some, especially those who are just getting started with OOP and design patterns.

In this tutorial, I will show you how to build a Flash site using PureMVC. If you’re familiar with the Flash IDE and have some experience with OOP and writing classes, you’re pretty much ready to go.

1. Download Source Code

Download the source files for this tutorial here.

2. Creating the Flash assets

Open fla/PureMVCSite.fla in the Flash IDE. In the Library Panel, you’ll see that I have created various assets for our Flash site:

  • A site movieclip that serves as a container clip with the UI elements laid out in the desired fashion
  • A header movieclip containing a dynamic textfield for the site header
  • A nav movieclip containing 3 navButton instances
  • A body movieclip containing a dynamic multiline textfield for the body content

The site movieclip symbol is linked to the class Site. The nav movieclip symbol is linked to the class MainNav. Both these classes belong to the package com.hubflanger.puremvc.view.component. In the PureMVC world, these are known as “view components”, not to be confused with the built-in Flash components. These components communicate with the PureMVC framework via their associated Mediators, allowing them to be “loosely-coupled”, thereby granting you great flexibility in changing their behavior without impacting the rest of your application.

In the ActionScript 3.0 Setting of the Publish Settings, you’ll see that Classpath has been set to point to “../as“. This tells the Flash compiler that the src/as folder is where you’ll look for the class files for this application.

3. Examining the PureMVC package

The PureMVC package org.puremvc.as3 (version 2.0.3) is located at src/as/org/puremvc/as3 and has been included for your convenience. The AS3 port of the PureMVC framework is hosted here. I strongly encourage you to spend some time reading the online documentation if possible.

At any time during this tutorial, if you feel the need for a clearer understanding of PureMVC terminologies such as Mediator, Proxy or Notification, please feel free to refer to these classes. Cliff Hall has done an excellent job of commenting them and the comments will provide a clear picture of each class’ role in the framework.

4. Examining PureMVCSite.as and ApplicationFacade.as

The main timeline of PureMVCSite.fla is linked to the document class PureMVCSite which is initialized when the application starts. PureMVCSite.as resides at the root of the as folder. When the application launches, PureMVCSite creates a Singleton instance of ApplicationFacade. The ApplicationFacade is the entry point to the PureMVC framework. When you instantiate ApplicationFacade, a whole slew of events take place behind the scenes to wire up your application with the PureMVC framework.

package
{
import com.hubflanger.puremvcsite.ApplicationFacade;
import flash.display.Sprite;

public class PureMVCSite extends Sprite
{
private var facade:ApplicationFacade;

public function PureMVCSite()
{
facade = ApplicationFacade.getInstance();
facade.startup( this.stage );
}
}
}

Let’s first examine the static constants defined at the beginning of this class. STARTUP, INITIALIZE_SITE and SECTION_CHANGED represent the Notification names of the events that our application will be responding to. A Notification is the default messaging deployed by PureMVC to inform the framework of an event that has been dispatched. In PureMVC land, sending a Notification is synonymous with broadcasting an event. Only difference is, it does a little more than just broadcasting it to everyone, whether they want to hear it or not. PureMVC finds the select audience who are “ticket holders” to an event, and drives them to the venue. I will explain this in greater detail later.

package com.hubflanger.puremvcsite
{
import org.puremvc.as3.interfaces.IFacade;
import org.puremvc.as3.patterns.facade.Facade;
import com.hubflanger.puremvcsite.controller.StartupCommand;

public class ApplicationFacade extends Facade implements IFacade
{
public static const STARTUP:String = "startup";
public static const INITIALIZE_SITE:String = "initializeSite";
public static const SECTION_CHANGED:String = "sectionChanged";

public static function getInstance() : ApplicationFacade
{
if ( instance == null ) instance = new ApplicationFacade();
return instance as ApplicationFacade;
}

override protected function initializeController() : void
{
super.initializeController();
registerCommand( STARTUP, StartupCommand );
}

public function startup( stage:Object ):void
{
sendNotification( STARTUP, stage );
}
}
}

ApplicationFacade overrides initializeController() to register StartupCommand with the STARTUP Notification. Behind the scenes, the Controller adds StartupCommand to its commandMap array and notes that StartupCommand is interested in listening for the STARTUP notification event.

The startup() method in ApplicationFacade is then explicitly called by PureMVCSite, passing in a reference to the Stage. This creates a Notification object with the name “startUp” and a reference to the Stage assigned to the its body property.

5. Examining StartupCommand.as

Upon receiving the Notification, the Controller iterates through its commandMap and retrieves StartupCommand as an object that is interested in the STARTUP notification. This results in the execute() method in StartupCommand being called.

package com.hubflanger.puremvcsite.controller
{
import flash.display.Stage;
import org.puremvc.as3.interfaces.ICommand;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;
import com.hubflanger.puremvcsite.ApplicationFacade;
import com.hubflanger.puremvcsite.view.StageMediator;
import com.hubflanger.puremvcsite.model.SiteDataProxy;

public class StartupCommand extends SimpleCommand implements ICommand
{
override public function execute( note:INotification ) : void
{
var stage:Stage = note.getBody() as Stage;
facade.registerMediator( new StageMediator( stage ) );
facade.registerProxy( new SiteDataProxy() );
}
}
}

The execute() method in StartupCommand retrieves the reference to the Stage from the Notification and passes that along to an instance of the StageMediator being created. It also creates an instance of SiteDataProxy.

facade is a built-in property of the Mediator and Proxy base classes from which StageMediator and SiteDataProxy extends respectively. It refers to the ApplicationFacade instance which extends the Facade base class.

facade.registerMediator() registers the newly created Mediator instance with the View which stores it in its mediatorMap. The Mediator instance can be retrieved during runtime via a simple reference of its static NAME property using facade.retrieveMediator().

Similarly, facade.registerProxy() registers the newly created Proxy instance with the Model which stores it in its proxyMap. The Proxy instance can also be retrieved by passing its NAME property to the facade.retrieveProxy() method.

At this point, you’ll probably start to notice a pattern here. The Model, View and Controller all have methods and properties that mirror each other, tying the Proxy to the Model, the Mediator to the View and the Command to the Controller. You’ll also notice that the Facade indeed provides a “shortcut” to accessing various parts of your application within the PureMVC framework. Nobody talks to the Model, View or Controller directly. Everybody goes through the middle man named Facade.

6. Examining StageMediator.as

The StageMediator facilitates the communication between the Stage and the PureMVC framework. The Stage instance is referenced via the viewComponent property inherited from the Mediator base class. In PureMVC convention, it is also commonplace to create an accessor method such as “get stage()“. Two very important methods of the Mediator instance are the listNotificationInterests() method and the handleNotification() method, which every Mediator subclass must override.

listNotificationInterests() returns an array of Notification names as defined in ApplicationFacade, representing events that this particular Mediator is interested in. When the View runs through its list of Observers, it checks each Mediator against its Notification interests. If there is a match pertaining to a specific Notification, the handleNotification() method of that Mediator is called. This is what I was referring to earlier by the select audience who are “ticket holders” to an event. In this case, our StageMediator is interested in the INITIALIZE_SITE Notification.

The Mediator base class extends Notifier which means that in addition to responding to Notifications, it is also capable of sending out Notifications via the sendNotification() method. In other words, it can dispatch events. Although, unlike a MovieClip, it can’t dispatch any Flash Events, instead, it dispatches Notification objects.

package com.hubflanger.puremvcsite.view
{
import com.hubflanger.puremvcsite.ApplicationFacade;
import com.hubflanger.puremvcsite.view.component.Site;
import flash.display.Stage;
import flash.events.MouseEvent;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.mediator.Mediator;

public class StageMediator extends Mediator implements IMediator
{
public static const NAME:String = "StageMediator";

public function StageMediator( viewComponent:Object )
{
super( NAME, viewComponent );
}

override public function listNotificationInterests():Array
{
return [
ApplicationFacade.INITIALIZE_SITE
];
}

override public function handleNotification( note:INotification ):void
{
switch ( note.getName() )
{
case ApplicationFacade.INITIALIZE_SITE:
initializeSite();
break;
}
}

private function initializeSite():void
{
var site:Site = new Site();
facade.registerMediator( new SiteMediator( site ) );
facade.registerMediator( new NavMediator( site.nav ) );
stage.addChild( site );

var navMediator:NavMediator = facade.retrieveMediator( NavMediator.NAME ) as NavMediator;
sendNotification( ApplicationFacade.SECTION_CHANGED, navMediator.currentSection );
}

protected function get stage():Stage
{
return viewComponent as Stage;
}
}
}

7. Examining SiteDataProxy.as

SiteDataProxy represents the data model for the application. It loads in dynamic data via xml and then parses and stores that information in a built-in property named “data“. Like the Mediator, the Proxy object also extends Notifier which makes it capable of sending out Notifications via the sendNotification() method. Unlike the Mediator, the Proxy does not have Notification interests and one can and should only update the Proxy via a Command.

package com.hubflanger.puremvcsite.model
{
import com.hubflanger.puremvcsite.ApplicationFacade;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
import org.puremvc.as3.interfaces.IProxy;
import org.puremvc.as3.patterns.proxy.Proxy;

public class SiteDataProxy extends Proxy implements IProxy
{
public static const NAME:String = "SpriteDataProxy";
public var navIDs:Array;

public function SiteDataProxy( )
{
super( NAME, new Object() );

var loader:URLLoader = new URLLoader();
loader.addEventListener( Event.COMPLETE, onDataLoaded );

try {
loader.load( new URLRequest( "data.xml" ));
} catch ( error:Error ) {
trace( "Unable to load requested document." );
}
}

private function onDataLoaded( evt:Event ):void
{
var xml:XML = new XML( evt.target.data );
xml.ignoreWhitespace = true;

data.header = xml.header.children().toXMLString();

var sections:XMLList = xml.sections.section;
navIDs = new Array();

for ( var i:uint=0; i<sections.length(); i++ )
{
var section:XML = sections[ i ];
var id:String = section.@id;
navIDs[ i ] = id;

var vo:SectionVO = new SectionVO( id,
section.@label,
section.content );
data[ id ] = vo;
}

sendNotification( ApplicationFacade.INITIALIZE_SITE );
}
}
}

When SiteDataProxy is done parsing the xml data, it sends out a INITIALIZE_SITE Notification, resulting in the handleNotification() method of StageMediator being called. This in turn calls the initializeSite() method which initializes the SiteMediator and NavMediator instances. These two Mediators serve to facilitate communication between the Site movieclip and the MainNav movieclip with the framework.

8. Examining SiteMediator.as

SiteMediator retrieves data from SiteDataProxy and calls site.init() to initialize the header text. It also declares an interest in the SECTION_CHANGED Notification event and calls site.updateBody() to update its content when such an event is dispatched.

package com.hubflanger.puremvcsite.view
{
import com.hubflanger.puremvcsite.ApplicationFacade;
import com.hubflanger.puremvcsite.model.*;
import com.hubflanger.puremvcsite.view.component.Site;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.mediator.Mediator;

public class SiteMediator extends Mediator implements IMediator
{
public static const NAME:String = "SiteMediator";
private var _siteDataProxy:SiteDataProxy;

public function SiteMediator( viewComponent:Object )
{
super( NAME, viewComponent );

_siteDataProxy = facade.retrieveProxy( SiteDataProxy.NAME ) as SiteDataProxy;
var data:Object = _siteDataProxy.getData();
site.init( data.header );
}

override public function listNotificationInterests():Array
{
return [
ApplicationFacade.SECTION_CHANGED
];
}

override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {
case ApplicationFacade.SECTION_CHANGED:
update( note.getBody() as String );
break;
}
}

private function update( s:String ):void
{
var data:Object = _siteDataProxy.getData();
var vo:SectionVO = data[ s ];
var content:XMLList = vo.content;

site.updateBody( content.toXMLString() );
}

protected function get site():Site
{
return viewComponent as Site;
}
}
}

9. Examining NavMediator.as and MainNav.as

NavMediator retrieves navigation id and label info from SiteDataProxy and passes that along to the MainNav instance, allowing MainNav to initialize its navButton instances. MainNav registers itself as an event listener of MOUSE_DOWN events triggered by the navButton instances.

NavMediator in turn registers itself as an event listener of the NAV_BUTTON_PRESSED UIEvent bubbled up by the MainNav instance. Upon receiving such an event, NavMediator checks it against its currentSection variable to determine if a SECTION_CHANGED Notification should be sent.

When the SECTION_CHANGED Notification is sent, SiteMediator will respond and update the content in the body movieclip, and NavMediator will update its navButton instances to display the correct state depending on each button’s id.

package com.hubflanger.puremvcsite.view
{
import com.hubflanger.puremvcsite.ApplicationFacade;
import com.hubflanger.puremvcsite.model.*;
import com.hubflanger.puremvcsite.view.component.MainNav;
import com.hubflanger.puremvcsite.view.event.UIEvent;
import org.puremvc.as3.interfaces.*;
import org.puremvc.as3.patterns.mediator.Mediator;

public class NavMediator extends Mediator implements IMediator
{
public static const NAME:String = "NavMediator";
private var _siteDataProxy:SiteDataProxy;
public var currentSection:String;

public function NavMediator( viewComponent:Object )
{
super( NAME, viewComponent );

_siteDataProxy = facade.retrieveProxy( SiteDataProxy.NAME ) as SiteDataProxy;
nav.addEventListener( UIEvent.NAV_BUTTON_PRESSED, onNavButtonPressed );

var data:Object = _siteDataProxy.getData();
var navIDs:Array = _siteDataProxy.navIDs;
var navLabels:Array = new Array();

currentSection = navIDs[ 0 ];

for ( var i:uint=0; i<navIDs.length; i++ )
{
var id:String = navIDs[ i ];
var vo:SectionVO = data[ id ];
navLabels[ id ] = vo.label;
}

nav.init( navIDs, navLabels );
}

override public function listNotificationInterests():Array
{
return [
ApplicationFacade.SECTION_CHANGED
];
}

override public function handleNotification( note:INotification ):void
{
switch ( note.getName() ) {

case ApplicationFacade.SECTION_CHANGED:
nav.update( note.getBody() as String );
break;
}
}

private function onNavButtonPressed( evt:UIEvent ):void
{
if ( evt.id != currentSection ) {
currentSection = evt.id;
sendNotification( ApplicationFacade.SECTION_CHANGED, evt.id );
}
}

protected function get nav():MainNav
{
return viewComponent as MainNav;
}
}
}
package com.hubflanger.puremvcsite.view.component
{
import com.hubflanger.puremvcsite.view.event.UIEvent;
import flash.display.MovieClip;
import flash.events.*;

public class MainNav extends MovieClip
{
public var btn0:MovieClip;
public var btn1:MovieClip;
public var btn2:MovieClip;
private var navButtons:Array;

public function MainNav()
{
navButtons = [ btn0, btn1, btn2 ];
}

public function init( navIDs:Array, labels:Array ):void
{
for ( var i:uint=0; i<navIDs.length; i++ )
{
var id:String = navIDs[ i ];
var btn:MovieClip = navButtons[ i ];
btn.id = id;
btn.txt.text = labels[ id ];
btn.buttonMode = true;
btn.mouseChildren = false;
btn.addEventListener( MouseEvent.MOUSE_DOWN, onMouseDownHandler );
}
}

public function update( s:String ):void
{
for ( var i:uint=0; i<navButtons.length; i++ )
{
var btn:MovieClip = navButtons[ i ];

if ( btn.id == s ) {
btn.txt.textColor = 0x4B1E18;
} else {
btn.txt.textColor = 0xFFFFFF;
}
}
}

private function onMouseDownHandler( evt:Event ):void
{
dispatchEvent( new UIEvent( UIEvent.NAV_BUTTON_PRESSED, evt.target.id ));
}
}
}

10. In Closing

This is pretty much the gist of what happens within a PureMVC Flash application. It is my hope that this real-world example will help you get a jump start on using the PureMVC framework for your Flash projects. I also hope this tutorial managed to demonstrate the value and power of the MVC design pattern. As you start developing applications of greater complexity, using a framework such as PureMVC will help immensely in keeping your classes loosely-coupled, and easier to scale and maintain. Happy coding!


  评论这张
 
阅读(372)| 评论(0)
推荐 转载

历史上的今天

评论

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

页脚

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