Friday, August 6, 2010MVP in Flex
Short post of how to implement MVP with Flex.
The english version of this post is shorter than portuguese, because all the code is commented in English
If you don't understand the MVP concepts, you should read this post from Martin Fowler.
Bellow the project structure
View: The login view component.
Components: CustomButton exposes only the methods we need
Interfaces: Interface defines what will be available to the presenter
Presenter: the login presenter
Model: The model interface and implementation.
Going to code.
Login.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel creationComplete="bindUi()" implements="interfaces.ILoginDisplay" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="180" xmlns:components="components.*">
<mx:Script>
<![CDATA[
import model.ILoginModel;
import model.LoginModel;
/********
* THE MOST IMPORTANT PART THAT SHOULD NOT BE FORGOTTEN IS TO CALL bingUi() METHOD
* IN creationComplete() OF YOUR VIEW.
* This is a login sample of how to use MVP in your flex applications.
* AUTOR : rrigoni@gmail.com
********/
import interfaces.ICustomButton;
import interfaces.ILoginDisplay;
import mx.controls.Alert;
import mx.core.IButton;
import presenters.LoginPresenter;
/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonClear():ICustomButton{
return this.clear;
}
/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonLogin():ICustomButton{
return this.loggin;
}
/**
* This method will be called when a login error occur.
* The presenter will send what was the error.
**/
public function displayErrorMessage(message:String):void{
Alert.show(message, "ERROR!");
}
/**
* This method will notify the user and perform with the login success.
* Its her responsability define what will be done.
**/
public function loginSuccess():void{
Alert.show("Login success with MVP.");
}
/**
* Bind the view with the presenter.
* Keep on eye on this method, need to called on creationComplete of your view!
**/
public function bindUi():void{
new LoginPresenter(this);
}
/**
* This method clean fields as needed
* How to clean, or witch fields need to be clean the view will define
**/
public function cleanFields():void{
this.username.text = null;
this.password.text = null;
}
/**
* Return the current model.
**/
public function model():ILoginModel{
return new LoginModel(username.text, password.text);
}
]]>
</mx:Script>
<mx:Form label="Login">
<mx:FormItem label="Username:">
<mx:TextInput id="username" />
</mx:FormItem>
<mx:FormItem label="Password:">
<mx:TextInput displayAsPassword="true" id="password" />
</mx:FormItem>
<mx:HBox>
<components:CustomButton id="loggin" label="Loggin" />
<components:CustomButton id="clear" label="Clear" />
</mx:HBox>
</mx:Form>
</mx:Panel>
CustomButton.as, custom button to exposes only the addEventListener method.
package components
{
import interfaces.ICustomButton;
import mx.controls.Button;
/**
*Custom Button, need extends Button and implement ICustomButton
* interface because we need to expose only the ICustomButton methodos
* to the presenter.
* @author ronaldo
*
*/
public class CustomButton extends Button implements ICustomButton{
public function CustomButton(){
}
}
}
ICustomButton.as, interface for the custombutton
package interfaces{
import flash.events.Event;
/**
* This interface exposes only the addEventListener function
* Until now is only we need to work.
* @author ronaldo
*
*/
public interface ICustomButton{
function addEventListener (type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void;
}
}
IDisplay.as, basic interface, all the view's interfaces need to implement the method bindUi() to bind the view with the presenter.
package interfaces{
public interface IDisplay{
/**
* Bind the view with the presenter, obey to call this method on creationComplete of your view.
*
*/
function bindUi():void;
}
}
ILoginDisplay, this interface is all the presenter need to know.
<pre>package interfaces{
import model.ILoginModel;
/**
* interface to Login.mxml
* Exposes only what you need to work.
* @author ronaldo
*
*/
public interface ILoginDisplay extends IDisplay{
/**
*Return the model
* @return the model
*
*/
function model():ILoginModel;
/**
*Exposes the clear button interface
* @return the button clear interface
*
*/
function buttonClear():ICustomButton;
/**
*Exposes the login button interface
* @return the login button interface
*
*/
function buttonLogin():ICustomButton;
/**
* Define in the view method to display error messages
* @param message The message to display.
*
*/
function displayErrorMessage(message:String):void;
/**
* The login logic remains on the presenter, this method notify
* the view to perform with login success.
*
*/
function loginSuccess():void;
/**
*
*Notify the view to clean the fields.
*/
function cleanFields():void;
}
}
LoginPresenter.as, Presenter will contain a instance of this interface, and the view implementation.
package presenters{
import flash.events.MouseEvent;
import interfaces.ILoginDisplay;
import model.ILoginModel;
import model.LoginModel;
/**
* Presenter for the Login.mxml view.
* @author ronaldo
*
*/
public class LoginPresenter{
/**
*The view instance
*/
private var dislpay:ILoginDisplay = null;
/**
* Construct the presenter instance and bind to the local view interface.
* @param view The view instance.
*
*/
public function LoginPresenter(view:ILoginDisplay){
this.dislpay = view;
addListeners();
}
/**
*Apply the listeners
*
*/
private function addListeners():void{
dislpay.buttonClear().addEventListener(MouseEvent.CLICK, buttonClear_eventHandler);
dislpay.buttonLogin().addEventListener(MouseEvent.CLICK, buttonlogin_eventHandler);
}
/**
* Button login event handler.
* Validate de login and notify the view.
* @param event The click event.
*
*/
private function buttonlogin_eventHandler(event:MouseEvent):void{
var mod:ILoginModel = dislpay.model();
if(mod.password && mod.password.length < 1 && mod.username && mod.username.length < 1){
dislpay.displayErrorMessage("Username and passwords cannot be blank!");
}
if(mod.username == 'admin' && mod.password == 'admin'){
dislpay.loginSuccess();
}else{
dislpay.displayErrorMessage("Wrong username and password.");
dislpay.cleanFields();
}
}
/**
* Button clear fields event handler,
* Notify the view when the button was pressed, and the view manage how to proceed.
* @param event The click event
*
*/
private function buttonClear_eventHandler(event:MouseEvent):void{
dislpay.cleanFields();
}
}
}
mvp.mxml, our Application.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" xmlns:view="view.*"> <mx:HBox verticalAlign="middle" horizontalAlign="center" width="100%" height="100%"> <view:Login /> </mx:HBox> </mx:Application>
ILoginModel.as, define the model interface.
package model{
public interface ILoginModel{
function get username():String;
function get password():String;
}
}
LoginModel.as, the model implementation.
package model{
public class LoginModel implements ILoginModel{
private var _password:String;
private var _username:String;
/**
*Generate new instance of LoginModel
* @param user The username
* @param pass The password
*
*/
public function LoginModel(user:String, pass:String){
this._password = pass;
this._username = user;
}
/**
*Return the username
* @return the username
*
*/
public function get username():String{
return _username;
}
/**
*Return the password.
* @return the password.
*
*/
public function get password():String{
return _password;
}
}
}
Any question, send me an email to rrigoni@gmail.com.
The source code is available mvp
Regards.
Ronaldo.




Português
Italiano
English